-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from oversizedev/develop
Add DateField, PhoneField, PriceField amd URLField
- Loading branch information
Showing
8 changed files
with
376 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,4 +21,3 @@ jobs: | |
env: | ||
GITHUB_TOKEN: ${{ secrets.ACTIONS_TOKEN }} | ||
WITH_V: false | ||
DEFAULT_BUMP: patch |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
// | ||
// Copyright © 2023 Alexander Romanov | ||
// DateField.swift, created on 26.02.2023 | ||
// | ||
|
||
import SwiftUI | ||
|
||
#if os(iOS) | ||
@available(iOS 15.0, *) | ||
@available(macOS, unavailable) | ||
@available(watchOS, unavailable) | ||
@available(tvOS, unavailable) | ||
public struct DateField: View { | ||
@Environment(\.theme) private var theme: ThemeSettings | ||
@Environment(\.fieldLabelPosition) private var fieldPlaceholderPosition: FieldLabelPosition | ||
@Binding private var selection: Date | ||
@Binding private var optionalSelection: Date? | ||
private let label: String | ||
@State private var showModal = false | ||
|
||
let isOptionalSelection: Bool | ||
|
||
public init( | ||
_ sheetTitle: String = "Date", | ||
selection: Binding<Date> | ||
) { | ||
label = sheetTitle | ||
_selection = selection | ||
_optionalSelection = .constant(nil) | ||
isOptionalSelection = false | ||
} | ||
|
||
public init( | ||
_ label: String = "Date", | ||
selection: Binding<Date?> | ||
) { | ||
self.label = label | ||
_selection = .constant(Date()) | ||
_optionalSelection = selection | ||
isOptionalSelection = true | ||
} | ||
|
||
public var body: some View { | ||
VStack(alignment: .leading, spacing: .xSmall) { | ||
if fieldPlaceholderPosition == .adjacent { | ||
HStack { | ||
Text(label) | ||
.subheadline(.medium) | ||
.foregroundColor(.onSurfaceHighEmphasis) | ||
Spacer() | ||
} | ||
} | ||
Button { | ||
showModal.toggle() | ||
} label: { | ||
VStack(alignment: .leading, spacing: .xxxSmall) { | ||
if fieldPlaceholderPosition == .overInput { | ||
Text(label) | ||
.font(.subheadline) | ||
.fontWeight(.semibold) | ||
.onSurfaceDisabledForegroundColor() | ||
} | ||
|
||
HStack { | ||
if isOptionalSelection, let optionalSelection { | ||
Text(optionalSelection.formatted(date: .long, time: .shortened)) | ||
} else if isOptionalSelection { | ||
Text(label) | ||
} else { | ||
Text(selection.formatted(date: .long, time: .shortened)) | ||
} | ||
Spacer() | ||
Icons.Base.calendar.outline | ||
// Icon(.calendar, color: .onSurfaceHighEmphasis) | ||
} | ||
} | ||
} | ||
.buttonStyle(.field) | ||
} | ||
.sheet(isPresented: $showModal) { | ||
if isOptionalSelection { | ||
DatePickerSheet(title: label, selection: $optionalSelection) | ||
.presentationDetents([.height(500)]) | ||
.presentationDragIndicator(.hidden) | ||
} else { | ||
DatePickerSheet(title: label, selection: $selection) | ||
.presentationDetents([.height(500)]) | ||
.presentationDragIndicator(.hidden) | ||
} | ||
} | ||
} | ||
} | ||
#endif |
70 changes: 70 additions & 0 deletions
70
Sources/OversizeUI/Controls/DateField/DatePickerSheet.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// | ||
// Copyright © 2021 Alexander Romanov | ||
// DatePickerSheet.swift, created on 11.12.2022 | ||
// | ||
|
||
import SwiftUI | ||
|
||
public struct DatePickerSheet: View { | ||
@Environment(\.screenSize) var screenSize | ||
@Environment(\.dismiss) var dismiss | ||
|
||
@Binding private var selection: Date | ||
@Binding private var optionalSelection: Date? | ||
@State private var date: Date | ||
|
||
private let title: String | ||
private var minimumDate: Date? | ||
|
||
public init(title: String, selection: Binding<Date>) { | ||
self.title = title | ||
_selection = selection | ||
_date = State(wrappedValue: selection.wrappedValue) | ||
_optionalSelection = .constant(nil) | ||
} | ||
|
||
public init(title: String, selection: Binding<Date?>) { | ||
self.title = title | ||
_date = State(wrappedValue: selection.wrappedValue ?? Date()) | ||
_optionalSelection = selection | ||
_selection = .constant(Date()) | ||
} | ||
|
||
public var body: some View { | ||
PageView(title) { | ||
SectionView { | ||
VStack { | ||
if let minimumDate { | ||
DatePicker("", selection: $date, in: minimumDate...) | ||
.datePickerStyle(.graphical) | ||
.labelsHidden() | ||
} else { | ||
DatePicker("", selection: $date) | ||
.datePickerStyle(.graphical) | ||
.labelsHidden() | ||
} | ||
} | ||
.padding(.horizontal, .small) | ||
.padding(.vertical, .xxxSmall) | ||
} | ||
.surfaceContentInsets(.zero) | ||
} | ||
.backgroundSecondary() | ||
.leadingBar { | ||
BarButton(.close) | ||
} | ||
.trailingBar { | ||
BarButton(.accent("Done", action: { | ||
selection = date | ||
optionalSelection = date | ||
dismiss() | ||
})) | ||
} | ||
} | ||
|
||
public func datePickerMinimumDate(_ date: Date) -> DatePickerSheet { | ||
var control = self | ||
control.minimumDate = date | ||
return control | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// | ||
// Copyright © 2023 Alexander Romanov | ||
// PhoneField.swift, created on 05.03.2023 | ||
// | ||
|
||
import SwiftUI | ||
|
||
#if os(iOS) | ||
@available(iOS 15.0, *) | ||
@available(macOS, unavailable) | ||
@available(watchOS, unavailable) | ||
@available(tvOS, unavailable) | ||
public struct PhoneField: View { | ||
@Binding private var phone: String | ||
|
||
@State private var textFieldHelper: FieldHelperStyle = .none | ||
|
||
public init(_ phone: Binding<String>) { | ||
_phone = phone | ||
} | ||
|
||
public var body: some View { | ||
TextField("+1 (000) 000 0000", text: $phone, onEditingChanged: { _ in | ||
textFieldHelper = .none | ||
}) {} | ||
.keyboardType(.phonePad) | ||
.textContentType(.nickname) | ||
.fieldHelper(.constant("Invalid Phone"), style: $textFieldHelper) | ||
} | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// | ||
// Copyright © 2023 Alexander Romanov | ||
// PriceField.swift, created on 15.03.2023 | ||
// | ||
|
||
import Foundation | ||
import SwiftUI | ||
|
||
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *) | ||
public struct PriceField: View { | ||
@Binding private var amount: Decimal | ||
private let currency: Locale.Currency | ||
|
||
public init(amount: Binding<Decimal>, currency: Locale.Currency) { | ||
_amount = amount | ||
self.currency = currency | ||
} | ||
|
||
public var body: some View { | ||
#if os(iOS) | ||
TextField( | ||
"0", | ||
value: $amount, | ||
format: .currency(code: currency.identifier) | ||
) | ||
.keyboardType(.decimalPad) | ||
.textFieldStyle(.default) | ||
#else | ||
TextField( | ||
"0", | ||
value: $amount, | ||
format: .currency(code: currency.identifier) | ||
) | ||
.textFieldStyle(.default) | ||
#endif | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// | ||
// Copyright © 2023 Alexander Romanov | ||
// URLField.swift | ||
// | ||
|
||
import SwiftUI | ||
|
||
#if os(iOS) | ||
@available(iOS 15.0, *) | ||
@available(macOS, unavailable) | ||
@available(watchOS, unavailable) | ||
@available(tvOS, unavailable) | ||
public struct URLField: View { | ||
@Binding private var url: URL? | ||
@State private var urlString: String = "" | ||
let title: String | ||
|
||
@State private var textFieldHelper: FieldHelperStyle = .none | ||
|
||
public init(_ title: String = "URL", url: Binding<URL?>) { | ||
self.title = title | ||
_url = url | ||
} | ||
|
||
public var body: some View { | ||
if #available(iOS 16.0, *) { | ||
TextField(title, value: $url, format: .url) | ||
.keyboardType(.URL) | ||
.textContentType(.URL) | ||
.textInputAutocapitalization(.never) | ||
.autocorrectionDisabled() | ||
} else { | ||
TextField(title, text: $urlString) | ||
.keyboardType(.URL) | ||
.textContentType(.URL) | ||
.textInputAutocapitalization(.never) | ||
.autocorrectionDisabled() | ||
.fieldHelper(.constant("Invalid URL"), style: $textFieldHelper) | ||
} | ||
} | ||
} | ||
#endif |
102 changes: 102 additions & 0 deletions
102
Sources/OversizeUI/Core/ViewModifier/HalfSheet/HalfSheet.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
// | ||
// Copyright © 2022 Alexander Romanov | ||
// HalfSheet.swift, created on 30.12.2022 | ||
// | ||
|
||
import SwiftUI | ||
#if canImport(UIKit) | ||
import UIKit | ||
#endif | ||
|
||
public enum Detents: Hashable { | ||
case large | ||
case medium | ||
case height(CGFloat) | ||
|
||
#if os(iOS) | ||
@available(iOS 15, *) | ||
public var uiViewDetents: UISheetPresentationController.Detent { | ||
switch self { | ||
case .large: | ||
return .large() | ||
case .medium: | ||
return .medium() | ||
case let .height(height): | ||
return height > 560 ? .large() : .medium() | ||
} | ||
} | ||
|
||
@available(iOS 16, *) | ||
func convertToSUI() -> PresentationDetent { | ||
switch self { | ||
case .large: | ||
return PresentationDetent.large | ||
case .medium: | ||
return PresentationDetent.medium | ||
case let .height(height): | ||
return PresentationDetent.height(height) | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
// swiftlint:disable line_length | ||
#if os(iOS) | ||
|
||
public struct SheetModifier: ViewModifier { | ||
public let detents: [Detents] | ||
public func body(content: Content) -> some View { | ||
SheetView(detents: detents) { | ||
content | ||
} | ||
} | ||
} | ||
|
||
public extension View { | ||
@_disfavoredOverload | ||
func presentationDetents(_ detents: [Detents]) -> some View { | ||
Group { | ||
if #available(iOS 16, *) { | ||
let suiDetents: Set<PresentationDetent> = Set(detents.compactMap { $0.convertToSUI() }) | ||
self.presentationDetents(suiDetents) | ||
} else { | ||
modifier(SheetModifier(detents: detents)) | ||
} | ||
} | ||
} | ||
} | ||
|
||
public struct SheetView<Content: View>: UIViewControllerRepresentable { | ||
private let content: Content | ||
private let detents: [Detents] | ||
|
||
public init(detents: [Detents], @ViewBuilder content: () -> Content) { | ||
self.content = content() | ||
self.detents = detents | ||
} | ||
|
||
public func makeUIViewController(context _: Context) -> SheetHostingController<Content> { | ||
SheetHostingController(rootView: content, detents: detents.map { $0.uiViewDetents }) | ||
} | ||
|
||
public func updateUIViewController(_: SheetHostingController<Content>, context _: Context) {} | ||
} | ||
|
||
public final class SheetHostingController<Content: View>: UIHostingController<Content> { | ||
var detents: [UISheetPresentationController.Detent] = [] | ||
|
||
override public func viewWillAppear(_ animated: Bool) { | ||
super.viewWillAppear(animated) | ||
if let controller = sheetPresentationController { | ||
controller.detents = detents | ||
} | ||
} | ||
} | ||
|
||
public extension SheetHostingController { | ||
convenience init(rootView: Content, detents: [UISheetPresentationController.Detent]) { | ||
self.init(rootView: rootView) | ||
self.detents = detents | ||
} | ||
} | ||
#endif |