Skip to content

Commit 52596d4

Browse files
author
Tabber
committed
Init
1 parent c21e89d commit 52596d4

File tree

3 files changed

+375
-4
lines changed

3 files changed

+375
-4
lines changed

Package.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.9
1+
// swift-tools-version: 5.5
22
// The swift-tools-version declares the minimum version of Swift required to build this package.
33

44
import PackageDescription

Sources/SCToolTip/SCToolTip.swift

+374-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,374 @@
1-
// The Swift Programming Language
2-
// https://docs.swift.org/swift-book
1+
import SwiftUI
2+
3+
@available(iOS 15.0, *)
4+
extension SCToolTipView {
5+
enum SCToolTipArrowAlignment {
6+
case TopLeft
7+
case TopCenter
8+
case TopRight
9+
case MidLeft
10+
case MidRight
11+
case BottomLeft
12+
case BottomCenter
13+
case BottomRight
14+
15+
16+
var degress: Angle {
17+
switch self {
18+
case .TopLeft, .TopCenter, .TopRight:
19+
return .degrees(0)
20+
case .MidLeft:
21+
return .degrees(-90)
22+
case .MidRight:
23+
return .degrees(90)
24+
case .BottomLeft, .BottomCenter, .BottomRight:
25+
return .degrees(180)
26+
}
27+
}
28+
}
29+
}
30+
31+
@available(iOS 15.0, *)
32+
struct SCToolTipView: View {
33+
34+
var description: String
35+
var arrowAlignment: SCToolTipArrowAlignment = .BottomCenter
36+
var showCancelButton: Bool = true
37+
38+
var onTapGesture: (() -> ())
39+
40+
var body: some View {
41+
42+
ZStack {
43+
44+
topLeftView
45+
topCenterView
46+
topRightView
47+
48+
midView
49+
50+
switch arrowAlignment {
51+
case .BottomLeft:
52+
bottomView(alignment: .leading)
53+
case .BottomCenter:
54+
bottomView(alignment: .center)
55+
case .BottomRight:
56+
bottomView(alignment: .trailing)
57+
default:
58+
EmptyView()
59+
}
60+
61+
}
62+
.contentShape(Rectangle())
63+
.onTapGesture {
64+
onTapGesture()
65+
}
66+
67+
68+
}
69+
70+
private var topLeftView: some View {
71+
VStack(alignment: .leading, spacing: -0.1) {
72+
73+
switch arrowAlignment {
74+
case .TopLeft:
75+
triangleView()
76+
.padding(.leading, 15)
77+
.zIndex(20)
78+
titleView
79+
default:
80+
EmptyView()
81+
}
82+
}
83+
}
84+
85+
private var topCenterView: some View {
86+
VStack(spacing: -0.1) {
87+
switch arrowAlignment {
88+
case .TopCenter:
89+
triangleView()
90+
.zIndex(20)
91+
titleView
92+
default:
93+
EmptyView()
94+
}
95+
}
96+
}
97+
98+
private var topRightView: some View {
99+
VStack(alignment: .trailing, spacing: -0.1) {
100+
switch arrowAlignment {
101+
case .TopRight:
102+
triangleView()
103+
.padding(.trailing, 15)
104+
.zIndex(20)
105+
titleView
106+
default:
107+
EmptyView()
108+
}
109+
}
110+
}
111+
112+
private var midView: some View {
113+
HStack(spacing: 0) {
114+
switch arrowAlignment {
115+
case .MidLeft:
116+
triangleView()
117+
.offset(x: 1.322, y: 0)
118+
.zIndex(20)
119+
titleView
120+
case .MidRight:
121+
titleView
122+
triangleView()
123+
.offset(x: -1.322, y: 0)
124+
.zIndex(20)
125+
default:
126+
EmptyView()
127+
}
128+
}
129+
}
130+
131+
private func bottomView(alignment: HorizontalAlignment) -> some View {
132+
VStack(alignment: alignment, spacing: -0.1) {
133+
switch arrowAlignment {
134+
case .BottomLeft:
135+
titleView
136+
triangleView()
137+
.padding(.leading, 15)
138+
.zIndex(20)
139+
case .BottomCenter:
140+
titleView
141+
triangleView()
142+
case .BottomRight:
143+
titleView
144+
triangleView()
145+
.padding(.trailing, 15)
146+
.zIndex(20)
147+
default:
148+
EmptyView()
149+
}
150+
}
151+
}
152+
153+
private func triangleView() -> some View {
154+
ZStack {
155+
156+
Triangle()
157+
.foregroundStyle((Color(hexString: "#484848") ?? .white).opacity(1))
158+
159+
TriangleBorder()
160+
.stroke(.white.opacity(0.7), lineWidth: 0.5)
161+
162+
}.frame(width: 8.69, height: 7)
163+
.rotationEffect(arrowAlignment.degress)
164+
}
165+
166+
private var titleView: some View {
167+
ZStack {
168+
RoundedRectangle(cornerRadius: 5)
169+
.foregroundStyle((Color(hexString: "#484848") ?? .white).opacity(1))
170+
171+
RoundedRectBorder(aligment: arrowAlignment)
172+
.stroke(.white.opacity(0.7), lineWidth: 0.5)
173+
.zIndex(1)
174+
175+
Text(description)
176+
.font(.system(size: 13, weight: .medium))
177+
.lineSpacing(5)
178+
.foregroundStyle(.white)
179+
.padding(.horizontal)
180+
.padding(.vertical, 8)
181+
.multilineTextAlignment(.center)
182+
183+
VStack {
184+
185+
HStack {
186+
Spacer()
187+
188+
Image(systemName: "xmark")
189+
.foregroundStyle(.white)
190+
.font(.system(size: 10, weight: .medium))
191+
.padding(.top, 5)
192+
.padding(.trailing, 3)
193+
}
194+
Spacer()
195+
196+
}
197+
.opacity(showCancelButton ? 1 : 0)
198+
}
199+
.fixedSize()
200+
}
201+
}
202+
203+
@available(iOS 13.0, *)
204+
extension Color {
205+
init?(hexString: String) {
206+
207+
let rgbaData = getrgbaData(hexString: hexString)
208+
209+
if(rgbaData != nil){
210+
211+
self.init(
212+
.sRGB,
213+
red: Double(rgbaData!.r),
214+
green: Double(rgbaData!.g),
215+
blue: Double(rgbaData!.b),
216+
opacity: Double(rgbaData!.a)
217+
)
218+
return
219+
}
220+
return nil
221+
}
222+
223+
224+
}
225+
226+
private func getrgbaData(hexString: String) -> (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat)? {
227+
228+
var rgbaData : (r: CGFloat, g: CGFloat, b: CGFloat, a: CGFloat)? = nil
229+
230+
if hexString.hasPrefix("#") {
231+
232+
let start = hexString.index(hexString.startIndex, offsetBy: 1)
233+
let hexColor = String(hexString[start...]) // Swift 4
234+
235+
let scanner = Scanner(string: hexColor)
236+
var hexNumber: UInt64 = 0
237+
238+
if scanner.scanHexInt64(&hexNumber) {
239+
240+
rgbaData = { // start of a closure expression that returns a Vehicle
241+
switch hexColor.count {
242+
case 8:
243+
244+
return ( r: CGFloat((hexNumber & 0xff000000) >> 24) / 255,
245+
g: CGFloat((hexNumber & 0x00ff0000) >> 16) / 255,
246+
b: CGFloat((hexNumber & 0x0000ff00) >> 8) / 255,
247+
a: CGFloat( hexNumber & 0x000000ff) / 255
248+
)
249+
case 6:
250+
251+
return ( r: CGFloat((hexNumber & 0xff0000) >> 16) / 255,
252+
g: CGFloat((hexNumber & 0x00ff00) >> 8) / 255,
253+
b: CGFloat((hexNumber & 0x0000ff)) / 255,
254+
a: 1.0
255+
)
256+
default:
257+
return nil
258+
}
259+
}()
260+
261+
}
262+
}
263+
264+
return rgbaData
265+
}
266+
267+
@available(iOS 15.0, *)
268+
struct RoundedRectBorder: Shape {
269+
270+
var aligment: SCToolTipView.SCToolTipArrowAlignment
271+
272+
init(aligment: SCToolTipView.SCToolTipArrowAlignment) {
273+
self.aligment = aligment
274+
}
275+
276+
func path(in rect: CGRect) -> Path {
277+
var path = Path()
278+
279+
switch aligment {
280+
case .BottomCenter:
281+
path = bottomCenterView(rect: rect)
282+
default:
283+
path = midLeftView(rect: rect)
284+
}
285+
286+
return path
287+
}
288+
289+
private func midLeftView(rect: CGRect) -> Path {
290+
var path = Path()
291+
292+
let centerX = rect.midX
293+
let bottomY = rect.maxY
294+
295+
path.addArc(center: CGPoint(x: rect.minX + 5, y: rect.minY + 5), radius: 5, startAngle: Angle(degrees: 280), endAngle: Angle(degrees: 180), clockwise: true)
296+
297+
path.addLine(to: CGPoint(x: rect.minX, y: bottomY - 5))
298+
299+
// Exclude the bottom center part from the border
300+
301+
path.addArc(center: CGPoint(x: rect.minX + 5, y: rect.maxY - 5), radius: 5, startAngle: Angle(degrees: -180), endAngle: Angle(degrees: 100), clockwise: true)
302+
303+
path.addLine(to: CGPoint(x: centerX, y: bottomY)) // Adjust the value as needed
304+
path.move(to: CGPoint(x: centerX, y: bottomY)) // Adjust the value as needed
305+
306+
path.addArc(center: CGPoint(x: rect.maxX - 5, y: rect.maxY - 5), radius: 5, startAngle: Angle(degrees: 100), endAngle: Angle(degrees: 0), clockwise: true)
307+
308+
path.addLine(to: CGPoint(x: rect.maxX, y: bottomY - 5))
309+
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + 5))
310+
311+
path.addArc(center: CGPoint(x: rect.maxX - 5, y: rect.minY + 5), radius: 5, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: -90), clockwise: true)
312+
313+
path.addLine(to: CGPoint(x: rect.minX + 5.9, y: rect.minY))
314+
315+
return path
316+
}
317+
318+
private func bottomCenterView(rect: CGRect) -> Path {
319+
320+
var path = Path()
321+
322+
let centerX = rect.midX
323+
let bottomY = rect.maxY
324+
325+
path.addArc(center: CGPoint(x: rect.minX + 5, y: rect.minY + 5), radius: 5, startAngle: Angle(degrees: 280), endAngle: Angle(degrees: 180), clockwise: true)
326+
327+
path.addLine(to: CGPoint(x: rect.minX, y: bottomY - 5))
328+
329+
// Exclude the bottom center part from the border
330+
331+
path.addArc(center: CGPoint(x: rect.minX + 5, y: rect.maxY - 5), radius: 5, startAngle: Angle(degrees: -180), endAngle: Angle(degrees: 100), clockwise: true)
332+
333+
path.addLine(to: CGPoint(x: centerX - 4, y: bottomY)) // Adjust the value as needed
334+
path.move(to: CGPoint(x: centerX + 4, y: bottomY)) // Adjust the value as needed
335+
336+
path.addArc(center: CGPoint(x: rect.maxX - 5, y: rect.maxY - 5), radius: 5, startAngle: Angle(degrees: 100), endAngle: Angle(degrees: 0), clockwise: true)
337+
338+
path.addLine(to: CGPoint(x: rect.maxX, y: bottomY - 5))
339+
path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + 5))
340+
341+
path.addArc(center: CGPoint(x: rect.maxX - 5, y: rect.minY + 5), radius: 5, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: -90), clockwise: true)
342+
343+
path.addLine(to: CGPoint(x: rect.minX + 5.9, y: rect.minY))
344+
345+
return path
346+
}
347+
}
348+
349+
@available(iOS 13.0, *)
350+
struct Triangle: Shape {
351+
func path(in rect: CGRect) -> Path {
352+
var path = Path()
353+
354+
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
355+
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
356+
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
357+
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
358+
return path
359+
}
360+
}
361+
362+
@available(iOS 13.0, *)
363+
struct TriangleBorder: Shape {
364+
func path(in rect: CGRect) -> Path {
365+
var path = Path()
366+
367+
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
368+
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
369+
path.addLine(to: CGPoint(x: rect.midX, y: rect.minY))
370+
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
371+
372+
return path
373+
}
374+
}

Tests/SCToolTipTests/SCToolTipTests.swift

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ final class SCToolTipTests: XCTestCase {
55
func testExample() throws {
66
// XCTest Documentation
77
// https://developer.apple.com/documentation/xctest
8-
98
// Defining Test Cases and Test Methods
109
// https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
1110
}

0 commit comments

Comments
 (0)