Skip to content

Commit 5631576

Browse files
husbigSvenKorset
andauthored
Common files has been moved (#18)
* common files has been moved * Update Sources/INCommons/SwiftUI/ColorPickerButton.swift Co-authored-by: Sven Korset <sven.korset@indie-software.com> * Color picker example has been added * fix clear button * Update Sources/INCommons/Swift/Extensions/String+Manipulation.swift Comment updated --------- Co-authored-by: Sven Korset <sven.korset@indie-software.com>
1 parent 33b2336 commit 5631576

File tree

8 files changed

+213
-3
lines changed

8 files changed

+213
-3
lines changed

INCommonsExample/INCommonsExample/HomeView.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ struct HomeView: View {
1717
ExampleViewFactory(title: "Overlay Button", view: { AnyView(OverlayButtonExample()) }),
1818
ExampleViewFactory(title: "Share Sheet", view: { AnyView(ShareSheet()) }),
1919
ExampleViewFactory(title: "View Condition", view: { AnyView(ViewConditions()) }),
20-
ExampleViewFactory(title: "View Frame", view: { AnyView(ViewFrame()) })
20+
ExampleViewFactory(title: "View Frame", view: { AnyView(ViewFrame()) }),
21+
ExampleViewFactory(title: "Color Picker", view: { AnyView(ColorPickerExample()) })
22+
2123
]
2224

2325
var body: some View {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import SwiftUI
2+
import INCommons
3+
4+
struct ColorPickerExample: View {
5+
@State var showColorPicker: Bool = false
6+
@State var selectedColor: Color = .green
7+
var body: some View {
8+
ColorPickerButton(color: selectedColor) {
9+
showColorPicker.toggle()
10+
}
11+
.colorPickerSheet(
12+
isPresented: $showColorPicker,
13+
selection: $selectedColor,
14+
supportsAlpha: false,
15+
title: "Color Picker Example",
16+
animated: true
17+
)
18+
}
19+
}

Sources/INCommons/Swift/Extensions/String+Manipulation.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
public extension String {
22
/// Represents the empty string `""`.
33
static let empty = ""
4+
/// Represents the new line string `\n`.
5+
static let newLine = "\n"
46

57
/**
68
Crops the string to a given number of characters, truncating the rest and replacing the last 3 characters with '...'.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import UIKit
2+
3+
/// The interface of the `ApplicationManager` which can be used to inject different implementations.
4+
/// Normally code should not be directly dependent upon the `UIApplication` because that might break UnitTests
5+
/// when running the tests on a device / simulator which differs from the test's expectations.
6+
/// Therefore, code should get the `UIApplication` instance being injected and for tests it should be mocked.
7+
/// It's expected that the implementation informs the `ObservableObject` of any value changes.
8+
@MainActor
9+
public protocol ApplicationManager {
10+
/// A boolean which refers `isIdleTimerDisabled`on UIApplication
11+
var isScreenLockEnabled: Bool { get }
12+
13+
/// Disables screen dimming automatically
14+
func disableScreenLock()
15+
16+
/// Enables screen dimming automatically
17+
func enableScreenLock()
18+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import UIKit
2+
3+
/// A concrete implementation of the `ApplicationManager` protocol which can be used in app code
4+
/// to return the corresponding values from `UIApplication`.
5+
@available(iOSApplicationExtension, unavailable)
6+
public final class ApplicationManagerLogic: ApplicationManager {
7+
public var isScreenLockEnabled: Bool {
8+
!UIApplication.shared.isIdleTimerDisabled
9+
}
10+
11+
public func disableScreenLock() {
12+
UIApplication.shared.isIdleTimerDisabled = true
13+
}
14+
15+
public func enableScreenLock() {
16+
UIApplication.shared.isIdleTimerDisabled = false
17+
}
18+
19+
public init() {}
20+
}

Sources/INCommons/SwiftUI/ClearButtonModifier.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ public struct ClearButtonModifier: ViewModifier {
77
let baseSize: CGSize
88
let tappableSize: CGSize
99
let image: Image
10+
1011
@Binding var text: String
12+
@Binding var isFocused: Bool
1113

1214
@ViewBuilder
1315
public func body(content: Content) -> some View {
1416
HStack {
1517
content
16-
if !text.isEmpty {
18+
if !text.isEmpty, isFocused {
1719
OverlayButton {
1820
text = .empty
1921
} baseShape: {
@@ -32,6 +34,7 @@ public struct ClearButtonModifier: ViewModifier {
3234
public extension View {
3335
func clearButton(
3436
text: Binding<String>,
37+
isFocused: Binding<Bool>,
3538
tintColor: Color,
3639
baseSize: CGSize,
3740
tappableSize: CGSize,
@@ -43,7 +46,8 @@ public extension View {
4346
baseSize: baseSize,
4447
tappableSize: tappableSize,
4548
image: image,
46-
text: text
49+
text: text,
50+
isFocused: isFocused
4751
)
4852
)
4953
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import SwiftUI
2+
3+
/// A re-created button view which looks like Apple's color picker button.
4+
/// The problem with Apple's color picker is that it's directly linked to the color picker view,
5+
/// so there is no way to navigate first to a different screen before showing Apple's color picker.
6+
/// To by-pass this shortcoming we can use this custom button.
7+
public struct ColorPickerButton: View {
8+
enum Constants {
9+
static let colorButtonSize: CGSize = .init(width: 36, height: 36)
10+
static let colorButtonInnerCircleSize: CGSize = .init(width: 26, height: 26)
11+
static let colorButtonEndAngle: CGFloat = 360
12+
static let colorButtonStrokeWidth: CGFloat = 3
13+
static let colorButtonGradient = Gradient(
14+
colors: [
15+
Color(uiColor: UIColor(hex: "E7E040") ?? .clear),
16+
Color(uiColor: UIColor(hex: "EEAA3C") ?? .clear),
17+
Color(uiColor: UIColor(hex: "E8403B") ?? .clear),
18+
Color(uiColor: UIColor(hex: "B33ED5") ?? .clear),
19+
Color(uiColor: UIColor(hex: "694AE8") ?? .clear),
20+
Color(uiColor: UIColor(hex: "3CCAE7") ?? .clear),
21+
Color(uiColor: UIColor(hex: "3CE885") ?? .clear),
22+
Color(uiColor: UIColor(hex: "89E743") ?? .clear),
23+
Color(uiColor: UIColor(hex: "E7E040") ?? .clear)
24+
]
25+
)
26+
}
27+
28+
private let color: Color
29+
private let action: () -> Void
30+
public init(color: Color, action: @escaping () -> Void) {
31+
self.color = color
32+
self.action = action
33+
}
34+
35+
public var body: some View {
36+
Button {
37+
action()
38+
} label: {
39+
ZStack {
40+
Circle()
41+
.frame(size: Constants.colorButtonInnerCircleSize)
42+
.foregroundStyle(color)
43+
Circle()
44+
.strokeBorder(
45+
AngularGradient(
46+
gradient: Constants.colorButtonGradient,
47+
center: .center,
48+
startAngle: .zero,
49+
endAngle: .degrees(Constants.colorButtonEndAngle)
50+
),
51+
lineWidth: Constants.colorButtonStrokeWidth
52+
)
53+
}
54+
.frame(size: Constants.colorButtonSize)
55+
}
56+
}
57+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import SwiftUI
2+
3+
public extension View {
4+
func colorPickerSheet(
5+
isPresented: Binding<Bool>,
6+
selection: Binding<Color>,
7+
supportsAlpha: Bool = true,
8+
title: String? = nil,
9+
animated: Bool = true
10+
) -> some View {
11+
background(
12+
ColorPickerSheet(
13+
isPresented: isPresented,
14+
selection: selection,
15+
supportsAlpha: supportsAlpha,
16+
title: title,
17+
animated: animated
18+
)
19+
)
20+
}
21+
}
22+
23+
private struct ColorPickerSheet: UIViewRepresentable {
24+
@Binding var isPresented: Bool
25+
@Binding var selection: Color
26+
let supportsAlpha: Bool
27+
let title: String?
28+
let animated: Bool
29+
func makeCoordinator() -> Coordinator {
30+
Coordinator(selection: $selection, isPresented: $isPresented)
31+
}
32+
33+
class Coordinator: NSObject, UIColorPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
34+
@Binding var selection: Color
35+
@Binding var isPresented: Bool
36+
var didPresent = false
37+
38+
init(selection: Binding<Color>, isPresented: Binding<Bool>) {
39+
_selection = selection
40+
_isPresented = isPresented
41+
}
42+
43+
func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
44+
selection = Color(viewController.selectedColor)
45+
}
46+
47+
func colorPickerViewControllerDidFinish(_: UIColorPickerViewController) {
48+
isPresented = false
49+
didPresent = false
50+
}
51+
52+
func presentationControllerDidDismiss(_: UIPresentationController) {
53+
isPresented = false
54+
didPresent = false
55+
}
56+
}
57+
58+
func getTopViewController(from view: UIView) -> UIViewController? {
59+
guard var top = view.window?.rootViewController else {
60+
return nil
61+
}
62+
while let next = top.presentedViewController {
63+
top = next
64+
}
65+
return top
66+
}
67+
68+
func makeUIView(context _: Context) -> UIView {
69+
let view = UIView()
70+
view.isHidden = true
71+
return view
72+
}
73+
74+
func updateUIView(_ uiView: UIView, context: Context) {
75+
if isPresented, !context.coordinator.didPresent {
76+
let modal = UIColorPickerViewController()
77+
modal.sheetPresentationController?.detents = [.medium(), .large()]
78+
modal.selectedColor = UIColor(selection)
79+
modal.supportsAlpha = supportsAlpha
80+
modal.title = title
81+
modal.delegate = context.coordinator
82+
modal.presentationController?.delegate = context.coordinator
83+
let top = getTopViewController(from: uiView)
84+
top?.present(modal, animated: true)
85+
context.coordinator.didPresent = true
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)