Skip to content

Commit

Permalink
Misc fixes (#26)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
bring-shrubbery authored Aug 28, 2022
1 parent 12aa572 commit 78d5f00
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 77 deletions.
132 changes: 82 additions & 50 deletions Sources/SwiftUITooltip/TooltipModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,27 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
@State private var contentHeight: CGFloat = 10

@State var animationOffset: CGFloat = 0
@State var animation: Optional<Animation> = nil

// MARK: - Computed properties

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

var arrowOffsetX: CGFloat {
switch config.side {
case .bottom, .center, .top:
return 0
case .leading:
case .left:
return (contentWidth / 2 + config.arrowHeight / 2)
case .leadingTop, .leadingBottom:
case .topLeft, .bottomLeft:
return (contentWidth / 2
+ config.arrowHeight / 2
- config.borderRadius / 2
- config.borderWidth / 2)
case .trailing:
case .right:
return -(contentWidth / 2 + config.arrowHeight / 2)
case .trailingTop, .trailingBottom:
case .topRight, .bottomRight:
return -(contentWidth / 2
+ config.arrowHeight / 2
- config.borderRadius / 2
Expand All @@ -57,18 +58,18 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {

var arrowOffsetY: CGFloat {
switch config.side {
case .leading, .center, .trailing:
case .left, .center, .right:
return 0
case .top:
return (contentHeight / 2 + config.arrowHeight / 2)
case .trailingTop, .leadingTop:
case .topRight, .topLeft:
return (contentHeight / 2
+ config.arrowHeight / 2
- config.borderRadius / 2
- config.borderWidth / 2)
case .bottom:
return -(contentHeight / 2 + config.arrowHeight / 2)
case .leadingBottom, .trailingBottom:
case .bottomLeft, .bottomRight:
return -(contentHeight / 2
+ config.arrowHeight / 2
- config.borderRadius / 2
Expand All @@ -80,9 +81,9 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {

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

private func offsetVertical(_ g: GeometryProxy) -> CGFloat {
switch config.side {
case .top, .trailingTop, .leadingTop:
case .top, .topRight, .topLeft:
return -(contentHeight + config.margin + actualArrowHeight + animationOffset)
case .bottom, .leadingBottom, .trailingBottom:
case .bottom, .bottomLeft, .bottomRight:
return g.size.height + config.margin + actualArrowHeight + animationOffset
case .leading, .center, .trailing:
case .left, .center, .right:
return (g.size.height - contentHeight) / 2
}
}
Expand All @@ -106,6 +107,7 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
if (config.enableAnimation) {
DispatchQueue.main.asyncAfter(deadline: .now() + config.animationTime) {
self.animationOffset = config.animationOffset
self.animation = config.animation
DispatchQueue.main.asyncAfter(deadline: .now() + config.animationTime*0.1) {
self.animationOffset = 0

Expand All @@ -121,66 +123,91 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
GeometryReader { g in
Text("")
.onAppear {
self.contentWidth = g.size.width
self.contentHeight = g.size.height
self.contentWidth = config.width ?? g.size.width
self.contentHeight = config.height ?? g.size.height
}
}
}

private var arrowView: some View {
return ArrowShape()
.rotation(Angle(radians: self.arrowRotation))
.stroke(self.config.borderColor)
guard let arrowAngle = config.side.getArrowAngleRadians() else {
return AnyView(EmptyView())
}

return AnyView(ArrowShape()
.rotation(Angle(radians: arrowAngle))
.stroke(config.borderColor)
.background(ArrowShape()
.offset(x: 0, y: 1)
.rotation(Angle(radians: self.arrowRotation))
.frame(width: self.config.arrowWidth+2, height: self.config.arrowHeight+1)
.foregroundColor(self.config.backgroundColor)
.rotation(Angle(radians: arrowAngle))
.frame(width: config.arrowWidth+2, height: config.arrowHeight+1)
.foregroundColor(config.backgroundColor)

).frame(width: self.config.arrowWidth, height: self.config.arrowHeight)
.offset(x: self.arrowOffsetX, y: self.arrowOffsetY)
).frame(width: config.arrowWidth, height: config.arrowHeight)
.offset(x: self.arrowOffsetX, y: self.arrowOffsetY))
}

private var arrowCutoutMask: some View {
return ZStack {
Rectangle()
.frame(
width: self.contentWidth + self.config.borderWidth * 2,
height: self.contentHeight + self.config.borderWidth * 2)
.foregroundColor(.white)
Rectangle()
.frame(
width: self.config.arrowWidth,
height: self.config.arrowHeight + self.config.borderWidth)
.rotationEffect(Angle(radians: self.arrowRotation))
.offset(
x: self.arrowOffsetX,
y: self.arrowOffsetY)
.foregroundColor(.black)
guard let arrowAngle = config.side.getArrowAngleRadians() else {
return AnyView(EmptyView())
}
.compositingGroup()
.luminanceToAlpha()

return AnyView(
ZStack {
Rectangle()
.frame(
width: self.contentWidth + config.borderWidth * 2,
height: self.contentHeight + config.borderWidth * 2)
.foregroundColor(.white)
Rectangle()
.frame(
width: config.arrowWidth,
height: config.arrowHeight + config.borderWidth)
.rotationEffect(Angle(radians: arrowAngle))
.offset(
x: self.arrowOffsetX,
y: self.arrowOffsetY)
.foregroundColor(.black)
}
.compositingGroup()
.luminanceToAlpha()
)
}

var tooltipBody: some View {
GeometryReader { g in
ZStack {
RoundedRectangle(cornerRadius: self.config.borderRadius)
.stroke(self.config.borderWidth == 0 ? Color.clear : self.config.borderColor)
.background(RoundedRectangle(cornerRadius: self.config.borderRadius)
.foregroundColor(self.config.backgroundColor))
.frame(width: self.contentWidth, height: self.contentHeight)
RoundedRectangle(cornerRadius: config.borderRadius)
.stroke(config.borderWidth == 0 ? Color.clear : config.borderColor)
.frame(
minWidth: contentWidth,
idealWidth: contentWidth,
maxWidth: config.width,
minHeight: contentHeight,
idealHeight: contentHeight,
maxHeight: config.height
)
.background(
RoundedRectangle(cornerRadius: config.borderRadius)
.foregroundColor(config.backgroundColor)
)
.mask(self.arrowCutoutMask)

ZStack {
content
.padding(self.config.contentPaddingEdgeInsets)
.fixedSize()
.padding(config.contentPaddingEdgeInsets)
.frame(
width: config.width,
height: config.height
)
.fixedSize(horizontal: config.width == nil, vertical: true)
}
.background(self.sizeMeasurer)
.overlay(self.arrowView)
.overlay(self.arrowView)
}
.offset(x: self.offsetHorizontal(g), y: self.offsetVertical(g))
.animation(self.animation)
.zIndex(config.zIndex)
.onAppear {
self.dispatchAnimation()
}
Expand All @@ -198,7 +225,12 @@ struct TooltipModifier<TooltipContent: View>: ViewModifier {
struct Tooltip_Previews: PreviewProvider {
static var previews: some View {
var config = DefaultTooltipConfig(side: .top)
config.backgroundColor = Color(red: 0.8, green: 0.9, blue: 1)
config.enableAnimation = false
// config.backgroundColor = Color(red: 0.8, green: 0.9, blue: 1)
// config.animationOffset = 10
// config.animationTime = 1
// config.width = 120
// config.height = 80


return VStack {
Expand Down
21 changes: 0 additions & 21 deletions Sources/SwiftUITooltip/TooltipSide.swift

This file was deleted.

54 changes: 48 additions & 6 deletions Sources/SwiftUITooltip/TooltipViewExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,39 +7,81 @@

import SwiftUI

// MARK: - with `enabled: Bool`
public extension View {
func tooltip<TooltipContent: View>(_ enabled: Bool = true, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
// Only enable parameter accessible
func tooltip<TooltipContent: View>(
_ enabled: Bool = true,
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
let config: TooltipConfig = DefaultTooltipConfig.shared

return modifier(TooltipModifier(enabled: enabled, config: config, content: content))
}

func tooltip<TooltipContent: View>(_ enabled: Bool = true, config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
// Only enable and config available
func tooltip<TooltipContent: View>(
_ enabled: Bool = true,
config: TooltipConfig,
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
modifier(TooltipModifier(enabled: enabled, config: config, content: content))
}

func tooltip<TooltipContent: View>(_ enabled: Bool = true, side: TooltipSide, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
// Enable and side are available
func tooltip<TooltipContent: View>(
_ enabled: Bool = true,
side: TooltipSide,
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
var config = DefaultTooltipConfig.shared
config.side = side

return modifier(TooltipModifier(enabled: enabled, config: config, content: content))
}

func tooltip<TooltipContent: View>(_ enabled: Bool = true, side: TooltipSide, config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
// Enable, side and config parameters available
func tooltip<TooltipContent: View>(
_ enabled: Bool = true,
side: TooltipSide,
config: TooltipConfig,
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
var config = config
config.side = side

return modifier(TooltipModifier(enabled: enabled, config: config, content: content))
}
}

func tooltip<TooltipContent: View>(_ side: TooltipSide, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
// MARK: - Without `enabled: Bool`
public extension View {
// No-parameter tooltip
func tooltip<TooltipContent: View>(
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
let config = DefaultTooltipConfig.shared

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

// Only side configurable
func tooltip<TooltipContent: View>(
_ side: TooltipSide,
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
var config = DefaultTooltipConfig.shared
config.side = side

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

func tooltip<TooltipContent: View>(_ side: TooltipSide, config: TooltipConfig, @ViewBuilder content: @escaping () -> TooltipContent) -> some View {
// Side and config are configurable
func tooltip<TooltipContent: View>(
_ side: TooltipSide,
config: TooltipConfig,
@ViewBuilder content: @escaping () -> TooltipContent
) -> some View {
var config = config
config.side = side

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public struct ArrowOnlyTooltipConfig: TooltipConfig {

public var side: TooltipSide = .bottom
public var margin: CGFloat = 8
public var zIndex: Double = 10000

public var width: CGFloat?
public var height: CGFloat?

public var borderRadius: CGFloat = 8
public var borderWidth: CGFloat = 0
Expand Down Expand Up @@ -39,6 +43,7 @@ public struct ArrowOnlyTooltipConfig: TooltipConfig {
public var enableAnimation: Bool = false
public var animationOffset: CGFloat = 10
public var animationTime: Double = 1
public var animation: Optional<Animation> = .easeInOut

public var transition: AnyTransition = .opacity

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public struct DefaultTooltipConfig: TooltipConfig {

public var side: TooltipSide = .bottom
public var margin: CGFloat = 8
public var zIndex: Double = 10000

public var width: CGFloat?
public var height: CGFloat?

public var borderRadius: CGFloat = 8
public var borderWidth: CGFloat = 2
Expand Down Expand Up @@ -39,6 +43,7 @@ public struct DefaultTooltipConfig: TooltipConfig {
public var enableAnimation: Bool = false
public var animationOffset: CGFloat = 10
public var animationTime: Double = 1
public var animation: Optional<Animation> = .easeInOut

public var transition: AnyTransition = .opacity

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ public protocol TooltipConfig {

var side: TooltipSide { get set }
var margin: CGFloat { get set }
var zIndex: Double { get set }

// MARK: - Sizes
var width: CGFloat? { get set }
var height: CGFloat? { get set }

// MARK: - Tooltip container

Expand Down Expand Up @@ -39,6 +44,7 @@ public protocol TooltipConfig {
var enableAnimation: Bool { get set }
var animationOffset: CGFloat { get set }
var animationTime: Double { get set }
var animation: Optional<Animation> { get set }

var transition: AnyTransition { get set }
}
Loading

0 comments on commit 78d5f00

Please sign in to comment.