Skip to content

Commit

Permalink
Merge pull request #8 from MrAsterisco/5-allow-customizing-font-for-p…
Browse files Browse the repository at this point in the history
…icker

Add font customization, fix manual input
  • Loading branch information
MrAsterisco authored Jul 6, 2022
2 parents f0bb696 + 7ef8129 commit 56c97d5
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ struct BasicView: View {
value: $otherSelection
)
.keyboardType(.numberPad)
#if os(iOS)
.pickerFont(.caption2)
#endif
}
.padding()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ struct ListView: View {
content: $content,
value: $selection
)

#if os(iOS)
.pickerFont(.headline)
#endif
.keyboardType(.numberPad)
.padding()

Expand Down
41 changes: 38 additions & 3 deletions Sources/ComboPicker/ComboPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ import SwiftUI
/// The predefined values are displayed in a `Picker` on all platforms, except for macOS, where
/// AppKit's `NSComboBox` is used.
///
/// - warning: On iOS and iPadOS, putting a picker below another one will cause the second picker
/// to take over all gestures and tap events of the first one. You can, however, fit two pickers on the same line.
/// - note: Please note that on tvOS, the amount of visible options in the Picker might be
/// limited, as all options are displayed inline.
///
Expand All @@ -64,7 +62,23 @@ import SwiftUI
/// ```
///
/// - note: Because of the `NSComboBox` works on macOS, predefined values will not be formatted using
/// this formatter. Their implementation of `LosslessStringConvertible` will be used instead.
/// this formatter. The model value must conform to `LosslessStringConvertible` and that implementation will be used instead.
///
/// On iOS, you can also change the font used to represent formatted values in the picker. You can either pass a custom font
/// or a text style that you'd like to use.
///
/// ```swift
/// ComboPicker(...)
/// .pickerFont(.init(name: "SomeFont", size: 21))
///
/// // or
///
/// ComboPicker(...)
/// .pickerFont(.headline)
/// ```
///
/// - note: The `pickerFont` method is only available on iOS. You cannot change the font on
/// watchOS, tvOS or macOS.
///
/// # Manual Input
/// When the user taps on the Picker, the component switches automatically to the manual input mode.
Expand All @@ -81,6 +95,9 @@ public struct ComboPicker<Model: ComboPickerModel, Formatter: ValueFormatterType
private let manualTitle: String

fileprivate var keyboardType = KeyboardType.default
#if os(iOS)
fileprivate var pickerFont: UIFont?
#endif
fileprivate var valueFormatter: Formatter

@Binding private var content: [Model]
Expand Down Expand Up @@ -124,9 +141,13 @@ public struct ComboPicker<Model: ComboPickerModel, Formatter: ValueFormatterType
valueFormatter: valueFormatter,
content: $content,
value: $value,
manualInput: $manualInput,
focus: _focus,
action: { change(to: .manual) }
)
#if os(iOS)
.font(pickerFont)
#endif
case .manual:
ManualInput(
title: manualTitle,
Expand Down Expand Up @@ -165,6 +186,20 @@ public extension ComboPicker {
newSelf.keyboardType = type
return newSelf
}

#if os(iOS)
@available(iOS 15, *)
func pickerFont(_ font: UIFont?) -> Self {
var newSelf = self
newSelf.pickerFont = font
return newSelf
}

@available(iOS 15, *)
func pickerFont(_ textStyle: UIFont.TextStyle) -> Self {
pickerFont(.preferredFont(forTextStyle: textStyle))
}
#endif
}

private extension ComboPicker {
Expand Down
5 changes: 3 additions & 2 deletions Sources/ComboPicker/Views/ManualInput.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ struct ManualInput: View {
#if os(iOS) || os(macOS)
HStack {
TextField(title, text: $value)
#if !os(macOS)
#if !os(macOS)
.keyboardType(keyboardType.systemType)
#endif
#endif
.font(.system(size: 21))
.multilineTextAlignment(.center)
.frame(height: 30)
Expand All @@ -49,6 +49,7 @@ struct ManualInput: View {
.background(Color.secondary.opacity(0.1))
.cornerRadius(8)
.padding([.leading, .trailing], 8)
.onSubmit(performAction)
}
#elseif os(watchOS)
HStack {
Expand Down
50 changes: 25 additions & 25 deletions Sources/ComboPicker/Views/SmartPicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,18 @@ import SwiftUI
struct SmartPicker<Model: ComboPickerModel, Formatter: ValueFormatterType>: View where Formatter.Value == Model {
private let title: String
private let manualTitle: String
private let valueFormatter: Formatter

private let keyboardType: KeyboardType
private let valueFormatter: Formatter

#if os(iOS)
fileprivate var font: UIFont?
#endif

@Binding private var content: [Model]
@Binding private var value: Model.Value

#if os(macOS) || os(tvOS)
@State private var comboBoxInput = ""
#endif
@Binding private var manualInput: String
@FocusState private var focus: ComboPickerMode?

let action: () -> ()
Expand All @@ -31,6 +33,7 @@ struct SmartPicker<Model: ComboPickerModel, Formatter: ValueFormatterType>: View
valueFormatter: Formatter,
content: Binding<[Model]>,
value: Binding<Model.Value>,
manualInput: Binding<String>,
focus: FocusState<ComboPickerMode?>,
action: @escaping () -> ()
){
Expand All @@ -40,6 +43,7 @@ struct SmartPicker<Model: ComboPickerModel, Formatter: ValueFormatterType>: View
self.valueFormatter = valueFormatter
self._content = content
self._value = value
self._manualInput = manualInput
self._focus = focus
self.action = action
}
Expand All @@ -55,19 +59,9 @@ struct SmartPicker<Model: ComboPickerModel, Formatter: ValueFormatterType>: View

ComboBox(
items: content.map { $0.value.description },
text: $comboBoxInput
text: $manualInput
)
}
.onChange(of: comboBoxInput) { newValue in
guard let modelValue = Model.Value(newValue) else { return }
value = modelValue
}
.onChange(of: value) { newValue in
comboBoxInput = newValue.description
}
.onAppear {
comboBoxInput = value.description
}
#elseif os(tvOS)
VStack {
Picker(title, selection: $value) {
Expand All @@ -77,16 +71,13 @@ struct SmartPicker<Model: ComboPickerModel, Formatter: ValueFormatterType>: View
}
}

TextField(manualTitle, text: $comboBoxInput)
TextField(manualTitle, text: $manualInput)
.font(.headline)
.keyboardType(keyboardType.systemType)
}
.onChange(of: comboBoxInput) { newValue in
guard let modelValue = Model.Value(newValue) else { return }
value = modelValue
}
.onChange(of: value) { newValue in
guard newValue != Model.Value(comboBoxInput) else { return }
comboBoxInput = ""
guard newValue != Model.Value(manualInput) else { return }
manualInput = ""
}
#elseif os(watchOS)
Picker(title, selection: $value) {
Expand All @@ -104,14 +95,23 @@ struct SmartPicker<Model: ComboPickerModel, Formatter: ValueFormatterType>: View
NativePicker(
content: $content,
selection: $value,
valueFormatter: valueFormatter
valueFormatter: valueFormatter,
font: font
)
.compositingGroup()
.contentShape(Rectangle())
.frame(height: Constants.pickerHeight)
.clipped()
.onTapGesture { action() }
.focused($focus, equals: .picker)
#endif
}
}

extension SmartPicker {
#if os(iOS)
func font(_ font: UIFont?) -> Self {
var newSelf = self
newSelf.font = font
return newSelf
}
#endif
}
26 changes: 23 additions & 3 deletions Sources/ComboPicker/Views/iOS/NativePicker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ struct NativePicker<Content: ComboPickerModel, Formatter: ValueFormatterType>: U
let content: Binding<[Content]>
var selection: Binding<Content.Value>

let font: UIFont?
let valueFormatter: Formatter

init(content: Binding<[Content]>, selection: Binding<Content.Value>, valueFormatter: Formatter) {
init(content: Binding<[Content]>, selection: Binding<Content.Value>, valueFormatter: Formatter, font: UIFont? = nil) {
self.content = content
self.selection = selection
self.valueFormatter = valueFormatter
self.font = font
}

func makeCoordinator() -> Self.Coordinator {
Expand Down Expand Up @@ -56,8 +58,26 @@ struct NativePicker<Content: ComboPickerModel, Formatter: ValueFormatterType>: U
return parent.content.wrappedValue.count
}

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return parent.valueFormatter.string(from: parent.content.wrappedValue[row])
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
let value = parent.valueFormatter.string(from: parent.content.wrappedValue[row])

if
let view = view as? UILabel,
(view.font == parent.font || parent.font == nil)
{
view.text = value
return view
}

let label = UILabel()
// Use the default text size for pickers, unless a different font is specified.
// This is necessary to mimic the standard behavior, when the delegate only returns strings.
label.font = parent.font ?? .systemFont(ofSize: 21)
label.text = value
label.textAlignment = .center
label.adjustsFontForContentSizeCategory = true

return label
}

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
Expand Down

0 comments on commit 56c97d5

Please sign in to comment.