Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion INCommonsExample/INCommonsExample/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ struct HomeView: View {
ExampleViewFactory(title: "Overlay Button", view: { AnyView(OverlayButtonExample()) }),
ExampleViewFactory(title: "Share Sheet", view: { AnyView(ShareSheet()) }),
ExampleViewFactory(title: "View Condition", view: { AnyView(ViewConditions()) }),
ExampleViewFactory(title: "View Frame", view: { AnyView(ViewFrame()) })
ExampleViewFactory(title: "View Frame", view: { AnyView(ViewFrame()) }),
ExampleViewFactory(title: "Color Picker", view: { AnyView(ColorPickerExample()) })

]

var body: some View {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import SwiftUI
import INCommons

struct ColorPickerExample: View {
@State var showColorPicker: Bool = false
@State var selectedColor: Color = .green
var body: some View {
ColorPickerButton(color: selectedColor) {
showColorPicker.toggle()
}
.colorPickerSheet(
isPresented: $showColorPicker,
selection: $selectedColor,
supportsAlpha: false,
title: "Color Picker Example",
animated: true
)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
public extension String {
/// Represents the empty string `""`.
static let empty = ""
/// Represents the new line string .
static let newLine = "\n"

/**
Crops the string to a given number of characters, truncating the rest and replacing the last 3 characters with '...'.
Expand Down
18 changes: 18 additions & 0 deletions Sources/INCommons/SwiftUI/ApplicationManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import UIKit

/// The interface of the `ApplicationManager` which can be used to inject different implementations.
/// Normally code should not be directly dependent upon the `UIApplication` because that might break UnitTests
/// when running the tests on a device / simulator which differs from the test's expectations.
/// Therefore, code should get the `UIApplication` instance being injected and for tests it should be mocked.
/// It's expected that the implementation informs the `ObservableObject` of any value changes.
@MainActor
public protocol ApplicationManager {
/// A boolean which refers `isIdleTimerDisabled`on UIApplication
var isScreenLockEnabled: Bool { get }

/// Disables screen dimming automatically
func disableScreenLock()

/// Enables screen dimming automatically
func enableScreenLock()
}
20 changes: 20 additions & 0 deletions Sources/INCommons/SwiftUI/ApplicationManagerLogic.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import UIKit

/// A concrete implementation of the `ApplicationManager` protocol which can be used in app code
/// to return the corresponding values from `UIApplication`.
@available(iOSApplicationExtension, unavailable)
public final class ApplicationManagerLogic: ApplicationManager {
public var isScreenLockEnabled: Bool {
!UIApplication.shared.isIdleTimerDisabled
}

public func disableScreenLock() {
UIApplication.shared.isIdleTimerDisabled = true
}

public func enableScreenLock() {
UIApplication.shared.isIdleTimerDisabled = false
}

public init() {}
}
57 changes: 57 additions & 0 deletions Sources/INCommons/SwiftUI/ColorPickerButton.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import SwiftUI

/// A re-created button view which looks like Apple's color picker button.
/// The problem with Apple's color picker is that it's directly linked to the color picker view,
/// so there is no way to navigate first to a different screen before showing Apple's color picker.
/// To by-pass this shortcoming we can use this custom button.
public struct ColorPickerButton: View {
enum Constants {
static let colorButtonSize: CGSize = .init(width: 36, height: 36)
static let colorButtonInnerCircleSize: CGSize = .init(width: 26, height: 26)
static let colorButtonEndAngle: CGFloat = 360
static let colorButtonStrokeWidth: CGFloat = 3
static let colorButtonGradient = Gradient(
colors: [
Color(uiColor: UIColor(hex: "E7E040") ?? .clear),
Color(uiColor: UIColor(hex: "EEAA3C") ?? .clear),
Color(uiColor: UIColor(hex: "E8403B") ?? .clear),
Color(uiColor: UIColor(hex: "B33ED5") ?? .clear),
Color(uiColor: UIColor(hex: "694AE8") ?? .clear),
Color(uiColor: UIColor(hex: "3CCAE7") ?? .clear),
Color(uiColor: UIColor(hex: "3CE885") ?? .clear),
Color(uiColor: UIColor(hex: "89E743") ?? .clear),
Color(uiColor: UIColor(hex: "E7E040") ?? .clear)
]
)
}

private let color: Color
private let action: () -> Void
public init(color: Color, action: @escaping () -> Void) {
self.color = color
self.action = action
}

public var body: some View {
Button {
action()
} label: {
ZStack {
Circle()
.frame(size: Constants.colorButtonInnerCircleSize)
.foregroundStyle(color)
Circle()
.strokeBorder(
AngularGradient(
gradient: Constants.colorButtonGradient,
center: .center,
startAngle: .zero,
endAngle: .degrees(Constants.colorButtonEndAngle)
),
lineWidth: Constants.colorButtonStrokeWidth
)
}
.frame(size: Constants.colorButtonSize)
}
}
}
88 changes: 88 additions & 0 deletions Sources/INCommons/SwiftUI/ColorPickerSheet.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import SwiftUI

public extension View {
func colorPickerSheet(
isPresented: Binding<Bool>,
selection: Binding<Color>,
supportsAlpha: Bool = true,
title: String? = nil,
animated: Bool = true
) -> some View {
background(
ColorPickerSheet(
isPresented: isPresented,
selection: selection,
supportsAlpha: supportsAlpha,
title: title,
animated: animated
)
)
}
}

private struct ColorPickerSheet: UIViewRepresentable {
@Binding var isPresented: Bool
@Binding var selection: Color
let supportsAlpha: Bool
let title: String?
let animated: Bool
func makeCoordinator() -> Coordinator {
Coordinator(selection: $selection, isPresented: $isPresented)
}

class Coordinator: NSObject, UIColorPickerViewControllerDelegate, UIAdaptivePresentationControllerDelegate {
@Binding var selection: Color
@Binding var isPresented: Bool
var didPresent = false

init(selection: Binding<Color>, isPresented: Binding<Bool>) {
_selection = selection
_isPresented = isPresented
}

func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) {
selection = Color(viewController.selectedColor)
}

func colorPickerViewControllerDidFinish(_: UIColorPickerViewController) {
isPresented = false
didPresent = false
}

func presentationControllerDidDismiss(_: UIPresentationController) {
isPresented = false
didPresent = false
}
}

func getTopViewController(from view: UIView) -> UIViewController? {
guard var top = view.window?.rootViewController else {
return nil
}
while let next = top.presentedViewController {
top = next
}
return top
}

func makeUIView(context _: Context) -> UIView {
let view = UIView()
view.isHidden = true
return view
}

func updateUIView(_ uiView: UIView, context: Context) {
if isPresented, !context.coordinator.didPresent {
let modal = UIColorPickerViewController()
modal.sheetPresentationController?.detents = [.medium(), .large()]
modal.selectedColor = UIColor(selection)
modal.supportsAlpha = supportsAlpha
modal.title = title
modal.delegate = context.coordinator
modal.presentationController?.delegate = context.coordinator
let top = getTopViewController(from: uiView)
top?.present(modal, animated: true)
context.coordinator.didPresent = true
}
}
}
Loading