Skip to content

Commit 78d5f00

Browse files
Misc fixes (#26)
* Impoved file structure * Refactored View extension to be more readable * Small refactors to have less logic in the TooltipModifier * Fixed animation issue #22 * Added zIndex to the config. Fixes #17 * Added width/height functionality to configuraton. Fixes #25
1 parent 12aa572 commit 78d5f00

File tree

8 files changed

+185
-77
lines changed

8 files changed

+185
-77
lines changed

Sources/SwiftUITooltip/TooltipModifier.swift

Lines changed: 82 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,27 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
2828
@State private var contentHeight: CGFloat = 10
2929

3030
@State var animationOffset: CGFloat = 0
31+
@State var animation: Optional<Animation> = nil
3132

3233
// MARK: - Computed properties
3334

34-
var arrowRotation: Double { Double(config.side.rawValue) * .pi / 4 }
35-
var actualArrowHeight: CGFloat { config.showArrow ? config.arrowHeight : 0 }
35+
var showArrow: Bool { config.showArrow && config.side.shouldShowArrow() }
36+
var actualArrowHeight: CGFloat { self.showArrow ? config.arrowHeight : 0 }
3637

3738
var arrowOffsetX: CGFloat {
3839
switch config.side {
3940
case .bottom, .center, .top:
4041
return 0
41-
case .leading:
42+
case .left:
4243
return (contentWidth / 2 + config.arrowHeight / 2)
43-
case .leadingTop, .leadingBottom:
44+
case .topLeft, .bottomLeft:
4445
return (contentWidth / 2
4546
+ config.arrowHeight / 2
4647
- config.borderRadius / 2
4748
- config.borderWidth / 2)
48-
case .trailing:
49+
case .right:
4950
return -(contentWidth / 2 + config.arrowHeight / 2)
50-
case .trailingTop, .trailingBottom:
51+
case .topRight, .bottomRight:
5152
return -(contentWidth / 2
5253
+ config.arrowHeight / 2
5354
- config.borderRadius / 2
@@ -57,18 +58,18 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
5758

5859
var arrowOffsetY: CGFloat {
5960
switch config.side {
60-
case .leading, .center, .trailing:
61+
case .left, .center, .right:
6162
return 0
6263
case .top:
6364
return (contentHeight / 2 + config.arrowHeight / 2)
64-
case .trailingTop, .leadingTop:
65+
case .topRight, .topLeft:
6566
return (contentHeight / 2
6667
+ config.arrowHeight / 2
6768
- config.borderRadius / 2
6869
- config.borderWidth / 2)
6970
case .bottom:
7071
return -(contentHeight / 2 + config.arrowHeight / 2)
71-
case .leadingBottom, .trailingBottom:
72+
case .bottomLeft, .bottomRight:
7273
return -(contentHeight / 2
7374
+ config.arrowHeight / 2
7475
- config.borderRadius / 2
@@ -80,9 +81,9 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
8081

8182
private func offsetHorizontal(_ g: GeometryProxy) -> CGFloat {
8283
switch config.side {
83-
case .leading, .leadingTop, .leadingBottom:
84+
case .left, .topLeft, .bottomLeft:
8485
return -(contentWidth + config.margin + actualArrowHeight + animationOffset)
85-
case .trailing, .trailingTop, .trailingBottom:
86+
case .right, .topRight, .bottomRight:
8687
return g.size.width + config.margin + actualArrowHeight + animationOffset
8788
case .top, .center, .bottom:
8889
return (g.size.width - contentWidth) / 2
@@ -91,11 +92,11 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
9192

9293
private func offsetVertical(_ g: GeometryProxy) -> CGFloat {
9394
switch config.side {
94-
case .top, .trailingTop, .leadingTop:
95+
case .top, .topRight, .topLeft:
9596
return -(contentHeight + config.margin + actualArrowHeight + animationOffset)
96-
case .bottom, .leadingBottom, .trailingBottom:
97+
case .bottom, .bottomLeft, .bottomRight:
9798
return g.size.height + config.margin + actualArrowHeight + animationOffset
98-
case .leading, .center, .trailing:
99+
case .left, .center, .right:
99100
return (g.size.height - contentHeight) / 2
100101
}
101102
}
@@ -106,6 +107,7 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
106107
if (config.enableAnimation) {
107108
DispatchQueue.main.asyncAfter(deadline: .now() + config.animationTime) {
108109
self.animationOffset = config.animationOffset
110+
self.animation = config.animation
109111
DispatchQueue.main.asyncAfter(deadline: .now() + config.animationTime*0.1) {
110112
self.animationOffset = 0
111113

@@ -121,66 +123,91 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
121123
GeometryReader { g in
122124
Text("")
123125
.onAppear {
124-
self.contentWidth = g.size.width
125-
self.contentHeight = g.size.height
126+
self.contentWidth = config.width ?? g.size.width
127+
self.contentHeight = config.height ?? g.size.height
126128
}
127129
}
128130
}
129131

130132
private var arrowView: some View {
131-
return ArrowShape()
132-
.rotation(Angle(radians: self.arrowRotation))
133-
.stroke(self.config.borderColor)
133+
guard let arrowAngle = config.side.getArrowAngleRadians() else {
134+
return AnyView(EmptyView())
135+
}
136+
137+
return AnyView(ArrowShape()
138+
.rotation(Angle(radians: arrowAngle))
139+
.stroke(config.borderColor)
134140
.background(ArrowShape()
135141
.offset(x: 0, y: 1)
136-
.rotation(Angle(radians: self.arrowRotation))
137-
.frame(width: self.config.arrowWidth+2, height: self.config.arrowHeight+1)
138-
.foregroundColor(self.config.backgroundColor)
142+
.rotation(Angle(radians: arrowAngle))
143+
.frame(width: config.arrowWidth+2, height: config.arrowHeight+1)
144+
.foregroundColor(config.backgroundColor)
139145

140-
).frame(width: self.config.arrowWidth, height: self.config.arrowHeight)
141-
.offset(x: self.arrowOffsetX, y: self.arrowOffsetY)
146+
).frame(width: config.arrowWidth, height: config.arrowHeight)
147+
.offset(x: self.arrowOffsetX, y: self.arrowOffsetY))
142148
}
143149

144150
private var arrowCutoutMask: some View {
145-
return ZStack {
146-
Rectangle()
147-
.frame(
148-
width: self.contentWidth + self.config.borderWidth * 2,
149-
height: self.contentHeight + self.config.borderWidth * 2)
150-
.foregroundColor(.white)
151-
Rectangle()
152-
.frame(
153-
width: self.config.arrowWidth,
154-
height: self.config.arrowHeight + self.config.borderWidth)
155-
.rotationEffect(Angle(radians: self.arrowRotation))
156-
.offset(
157-
x: self.arrowOffsetX,
158-
y: self.arrowOffsetY)
159-
.foregroundColor(.black)
151+
guard let arrowAngle = config.side.getArrowAngleRadians() else {
152+
return AnyView(EmptyView())
160153
}
161-
.compositingGroup()
162-
.luminanceToAlpha()
154+
155+
return AnyView(
156+
ZStack {
157+
Rectangle()
158+
.frame(
159+
width: self.contentWidth + config.borderWidth * 2,
160+
height: self.contentHeight + config.borderWidth * 2)
161+
.foregroundColor(.white)
162+
Rectangle()
163+
.frame(
164+
width: config.arrowWidth,
165+
height: config.arrowHeight + config.borderWidth)
166+
.rotationEffect(Angle(radians: arrowAngle))
167+
.offset(
168+
x: self.arrowOffsetX,
169+
y: self.arrowOffsetY)
170+
.foregroundColor(.black)
171+
}
172+
.compositingGroup()
173+
.luminanceToAlpha()
174+
)
163175
}
164176

165177
var tooltipBody: some View {
166178
GeometryReader { g in
167179
ZStack {
168-
RoundedRectangle(cornerRadius: self.config.borderRadius)
169-
.stroke(self.config.borderWidth == 0 ? Color.clear : self.config.borderColor)
170-
.background(RoundedRectangle(cornerRadius: self.config.borderRadius)
171-
.foregroundColor(self.config.backgroundColor))
172-
.frame(width: self.contentWidth, height: self.contentHeight)
180+
RoundedRectangle(cornerRadius: config.borderRadius)
181+
.stroke(config.borderWidth == 0 ? Color.clear : config.borderColor)
182+
.frame(
183+
minWidth: contentWidth,
184+
idealWidth: contentWidth,
185+
maxWidth: config.width,
186+
minHeight: contentHeight,
187+
idealHeight: contentHeight,
188+
maxHeight: config.height
189+
)
190+
.background(
191+
RoundedRectangle(cornerRadius: config.borderRadius)
192+
.foregroundColor(config.backgroundColor)
193+
)
173194
.mask(self.arrowCutoutMask)
174195

175196
ZStack {
176197
content
177-
.padding(self.config.contentPaddingEdgeInsets)
178-
.fixedSize()
198+
.padding(config.contentPaddingEdgeInsets)
199+
.frame(
200+
width: config.width,
201+
height: config.height
202+
)
203+
.fixedSize(horizontal: config.width == nil, vertical: true)
179204
}
180205
.background(self.sizeMeasurer)
181-
.overlay(self.arrowView)
206+
.overlay(self.arrowView)
182207
}
183208
.offset(x: self.offsetHorizontal(g), y: self.offsetVertical(g))
209+
.animation(self.animation)
210+
.zIndex(config.zIndex)
184211
.onAppear {
185212
self.dispatchAnimation()
186213
}
@@ -198,7 +225,12 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
198225
struct Tooltip_Previews: PreviewProvider {
199226
static var previews: some View {
200227
var config = DefaultTooltipConfig(side: .top)
201-
config.backgroundColor = Color(red: 0.8, green: 0.9, blue: 1)
228+
config.enableAnimation = false
229+
// config.backgroundColor = Color(red: 0.8, green: 0.9, blue: 1)
230+
// config.animationOffset = 10
231+
// config.animationTime = 1
232+
// config.width = 120
233+
// config.height = 80
202234

203235

204236
return VStack {

Sources/SwiftUITooltip/TooltipSide.swift

Lines changed: 0 additions & 21 deletions
This file was deleted.

Sources/SwiftUITooltip/TooltipViewExtension.swift

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,39 +7,81 @@
77

88
import SwiftUI
99

10+
// MARK: - with `enabled: Bool`
1011
public extension View {
11-
func tooltip<TooltipContent: View>(_ enabled: Bool = true, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
12+
// Only enable parameter accessible
13+
func tooltip<TooltipContent: View>(
14+
_ enabled: Bool = true,
15+
@ViewBuilder content: @escaping () -> TooltipContent
16+
) -> some View {
1217
let config: TooltipConfig = DefaultTooltipConfig.shared
1318

1419
return modifier(TooltipModifier(enabled: enabled, config: config, content: content))
1520
}
1621

17-
func tooltip<TooltipContent: View>(_ enabled: Bool = true, config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
22+
// Only enable and config available
23+
func tooltip<TooltipContent: View>(
24+
_ enabled: Bool = true,
25+
config: TooltipConfig,
26+
@ViewBuilder content: @escaping () -> TooltipContent
27+
) -> some View {
1828
modifier(TooltipModifier(enabled: enabled, config: config, content: content))
1929
}
2030

21-
func tooltip<TooltipContent: View>(_ enabled: Bool = true, side: TooltipSide, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
31+
// Enable and side are available
32+
func tooltip<TooltipContent: View>(
33+
_ enabled: Bool = true,
34+
side: TooltipSide,
35+
@ViewBuilder content: @escaping () -> TooltipContent
36+
) -> some View {
2237
var config = DefaultTooltipConfig.shared
2338
config.side = side
2439

2540
return modifier(TooltipModifier(enabled: enabled, config: config, content: content))
2641
}
2742

28-
func tooltip<TooltipContent: View>(_ enabled: Bool = true, side: TooltipSide, config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
43+
// Enable, side and config parameters available
44+
func tooltip<TooltipContent: View>(
45+
_ enabled: Bool = true,
46+
side: TooltipSide,
47+
config: TooltipConfig,
48+
@ViewBuilder content: @escaping () -> TooltipContent
49+
) -> some View {
2950
var config = config
3051
config.side = side
3152

3253
return modifier(TooltipModifier(enabled: enabled, config: config, content: content))
3354
}
55+
}
3456

35-
func tooltip<TooltipContent: View>(_ side: TooltipSide, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
57+
// MARK: - Without `enabled: Bool`
58+
public extension View {
59+
// No-parameter tooltip
60+
func tooltip<TooltipContent: View>(
61+
@ViewBuilder content: @escaping () -> TooltipContent
62+
) -> some View {
63+
let config = DefaultTooltipConfig.shared
64+
65+
return modifier(TooltipModifier(enabled: true, config: config, content: content))
66+
}
67+
68+
// Only side configurable
69+
func tooltip<TooltipContent: View>(
70+
_ side: TooltipSide,
71+
@ViewBuilder content: @escaping () -> TooltipContent
72+
) -> some View {
3673
var config = DefaultTooltipConfig.shared
3774
config.side = side
3875

3976
return modifier(TooltipModifier(enabled: true, config: config, content: content))
4077
}
4178

42-
func tooltip<TooltipContent: View>(_ side: TooltipSide, config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
79+
// Side and config are configurable
80+
func tooltip<TooltipContent: View>(
81+
_ side: TooltipSide,
82+
config: TooltipConfig,
83+
@ViewBuilder content: @escaping () -> TooltipContent
84+
) -> some View {
4385
var config = config
4486
config.side = side
4587

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public struct ArrowOnlyTooltipConfig: TooltipConfig {
1212

1313
public var side: TooltipSide = .bottom
1414
public var margin: CGFloat = 8
15+
public var zIndex: Double = 10000
16+
17+
public var width: CGFloat?
18+
public var height: CGFloat?
1519

1620
public var borderRadius: CGFloat = 8
1721
public var borderWidth: CGFloat = 0
@@ -39,6 +43,7 @@ public struct ArrowOnlyTooltipConfig: TooltipConfig {
3943
public var enableAnimation: Bool = false
4044
public var animationOffset: CGFloat = 10
4145
public var animationTime: Double = 1
46+
public var animation: Optional<Animation> = .easeInOut
4247

4348
public var transition: AnyTransition = .opacity
4449

Sources/SwiftUITooltip/TooltipConfigurations/DefaultTooltipConfig.swift renamed to Sources/SwiftUITooltip/config/DefaultTooltipConfig.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ public struct DefaultTooltipConfig: TooltipConfig {
1212

1313
public var side: TooltipSide = .bottom
1414
public var margin: CGFloat = 8
15+
public var zIndex: Double = 10000
16+
17+
public var width: CGFloat?
18+
public var height: CGFloat?
1519

1620
public var borderRadius: CGFloat = 8
1721
public var borderWidth: CGFloat = 2
@@ -39,6 +43,7 @@ public struct DefaultTooltipConfig: TooltipConfig {
3943
public var enableAnimation: Bool = false
4044
public var animationOffset: CGFloat = 10
4145
public var animationTime: Double = 1
46+
public var animation: Optional<Animation> = .easeInOut
4247

4348
public var transition: AnyTransition = .opacity
4449

Sources/SwiftUITooltip/TooltipConfigurations/TooltipConfig.swift renamed to Sources/SwiftUITooltip/config/TooltipConfig.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ public protocol TooltipConfig {
1212

1313
var side: TooltipSide { get set }
1414
var margin: CGFloat { get set }
15+
var zIndex: Double { get set }
16+
17+
// MARK: - Sizes
18+
var width: CGFloat? { get set }
19+
var height: CGFloat? { get set }
1520

1621
// MARK: - Tooltip container
1722

@@ -39,6 +44,7 @@ public protocol TooltipConfig {
3944
var enableAnimation: Bool { get set }
4045
var animationOffset: CGFloat { get set }
4146
var animationTime: Double { get set }
47+
var animation: Optional<Animation> { get set }
4248

4349
var transition: AnyTransition { get set }
4450
}

0 commit comments

Comments
 (0)