-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
739 additions
and
435 deletions.
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
Demo/Demo/Gravatar-Demo/Common/BaseFormViewController.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,84 @@ | ||
import UIKit | ||
import Combine | ||
|
||
struct FormSection: SectionTitle, Hashable { | ||
var sectionTitle: String | ||
} | ||
|
||
class FormField: NSObject, @unchecked Sendable { | ||
@MainActor | ||
func dequeueCell(in tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { fatalError() } | ||
} | ||
|
||
class BaseFormViewController: UITableViewController { | ||
@Published var fieldText: String = "" | ||
|
||
var cancellables = Set<AnyCancellable>() | ||
|
||
let section = FormSection(sectionTitle: "") | ||
|
||
var snapshot = NSDiffableDataSourceSnapshot<FormSection, FormField>() | ||
lazy var dataSource: UITableViewDiffableDataSource = SectionTitleTableViewDiffibleDataSource<FormSection, FormField>(tableView: tableView) { | ||
(tableView: UITableView, indexPath: IndexPath, formField: FormField) -> UITableViewCell? in | ||
let cell = formField.dequeueCell(in: tableView, for: indexPath) | ||
cell.backgroundView?.backgroundColor = .secondarySystemBackground | ||
cell.backgroundColor = .secondarySystemBackground | ||
cell.contentView.backgroundColor = .secondarySystemBackground | ||
cell.accessoryView?.backgroundColor = .secondarySystemBackground | ||
return cell | ||
} | ||
|
||
var form: [FormField] { [] } | ||
|
||
override func viewDidLoad() { | ||
super.viewDidLoad() | ||
tableView.allowsSelection = false | ||
snapshot.appendSections([section]) | ||
snapshot.appendItems(form) | ||
dataSource.apply(snapshot) | ||
view.backgroundColor = .secondarySystemBackground | ||
tableView.separatorStyle = .none | ||
tableView.rowHeight = UITableView.automaticDimension | ||
tableView.delaysContentTouches = false | ||
} | ||
|
||
func replace(_ oldFormField: FormField, with newFormField: FormField, after: FormField) { | ||
snapshot.deleteItems([oldFormField]) | ||
if snapshot.indexOfItem(newFormField) == nil { | ||
snapshot.insertItems([newFormField], afterItem: after) | ||
} | ||
dataSource.apply(snapshot) | ||
} | ||
|
||
func update(_ field: FormField, animated: Bool = false) { | ||
update([field]) | ||
} | ||
|
||
func update(_ fields: [FormField], animated: Bool = false) { | ||
snapshot.reloadItems(fields) | ||
dataSource.apply(snapshot, animatingDifferences: animated) | ||
} | ||
|
||
|
||
} | ||
|
||
class SectionTitleTableViewDiffibleDataSource<SectionType: Hashable, ItemType: Hashable>: UITableViewDiffableDataSource<SectionType, ItemType> where SectionType: SectionTitle, SectionType: Sendable, ItemType: Sendable { | ||
|
||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { | ||
return sectionIdentifier(for: section)?.sectionTitle | ||
} | ||
} | ||
|
||
protocol SectionTitle { | ||
var sectionTitle: String { get } | ||
} | ||
|
||
extension UIControl { | ||
func removeAllActions() { | ||
enumerateEventHandlers { action, _, event, _ in | ||
if let action = action { | ||
removeAction(action, for: event) | ||
} | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
Demo/Demo/Gravatar-Demo/Common/FormFields/ButtonField.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,56 @@ | ||
import UIKit | ||
|
||
final class ButtonField: FormField, @unchecked Sendable { | ||
var title: String | ||
var isActionButton: Bool | ||
var isEnabled: Bool | ||
|
||
private let cellID = "ButtonCell" | ||
private let action: UIAction | ||
|
||
@MainActor | ||
init(title: String, isActionButton: Bool = false, enabled: Bool = true, action actionHandler: @escaping UIActionHandler) { | ||
self.title = title | ||
self.isActionButton = isActionButton | ||
self.isEnabled = enabled | ||
self.action = UIAction(handler: actionHandler) | ||
} | ||
|
||
@MainActor | ||
override func dequeueCell(in tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { | ||
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? ButtonCell ?? ButtonCell(reuseIdentifier: cellID) | ||
cell.update(with: self) | ||
cell.button.removeAllActions() | ||
cell.button.addAction(action, for: .touchUpInside) | ||
return cell | ||
} | ||
} | ||
|
||
private final class ButtonCell: UITableViewCell { | ||
let button = UIButton() | ||
|
||
init(reuseIdentifier: String?) { | ||
super.init(style: .default, reuseIdentifier: reuseIdentifier) | ||
button.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
self.contentView.addSubview(button) | ||
NSLayoutConstraint.activate([ | ||
contentView.centerXAnchor.constraint(equalTo: button.centerXAnchor), | ||
contentView.topAnchor.constraint(equalTo: button.topAnchor), | ||
contentView.bottomAnchor.constraint(equalTo: button.bottomAnchor, constant: 8), | ||
]) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
func update(with field: ButtonField) { | ||
var config = field.isActionButton ? UIButton.Configuration.borderedProminent() : .plain() | ||
config.title = field.title | ||
button.configuration = config | ||
button.isEnabled = field.isEnabled | ||
button.sizeToFit() | ||
} | ||
} | ||
|
56 changes: 56 additions & 0 deletions
56
Demo/Demo/Gravatar-Demo/Common/FormFields/ButtonLabelField.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,56 @@ | ||
import UIKit | ||
|
||
final class ButtonLabelField: FormField, @unchecked Sendable { | ||
var buttonTitle: String | ||
var title: String | ||
var subtitle: String? | ||
var isEnabled: Bool | ||
|
||
private let cellID = "ButtonCellCell" | ||
private let action: UIAction | ||
|
||
@MainActor | ||
init(title: String, subtitle: String?, buttonTitle: String, isEnabled: Bool = true, action actionHandler: @escaping UIActionHandler) { | ||
self.title = title | ||
self.subtitle = subtitle | ||
self.buttonTitle = buttonTitle | ||
self.isEnabled = isEnabled | ||
self.action = UIAction(handler: actionHandler) | ||
} | ||
|
||
@MainActor | ||
override func dequeueCell(in tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { | ||
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? ButtonLabelCell ?? ButtonLabelCell(reuseIdentifier: cellID) | ||
cell.update(with: self) | ||
cell.button.removeAllActions() | ||
cell.button.addAction(action, for: .touchUpInside) | ||
return cell | ||
} | ||
} | ||
|
||
private final class ButtonLabelCell: UITableViewCell { | ||
let button = UIButton() | ||
|
||
init(reuseIdentifier: String?) { | ||
super.init(style: .default, reuseIdentifier: reuseIdentifier) | ||
accessoryView = button | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
func update(with field: ButtonLabelField) { | ||
var config = UIButton.Configuration.plain() | ||
config.title = field.buttonTitle | ||
button.configuration = config | ||
button.sizeToFit() | ||
button.isEnabled = field.isEnabled | ||
|
||
var cellConfig = self.defaultContentConfiguration() | ||
cellConfig.text = field.title | ||
cellConfig.secondaryText = field.subtitle | ||
|
||
self.contentConfiguration = cellConfig | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
Demo/Demo/Gravatar-Demo/Common/FormFields/ImageFormField.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,62 @@ | ||
import UIKit | ||
import Combine | ||
|
||
class ImageFormField: FormField, @unchecked Sendable, UITextFieldDelegate { | ||
var image: UIImage? | ||
var size: CGSize | ||
private(set) var imageView: UIImageView? | ||
private var cancellables = Set<AnyCancellable>() | ||
|
||
private let cellID = "ImageFormCell" | ||
|
||
init(image: UIImage? = nil, size: CGSize) { | ||
self.image = image | ||
self.size = size | ||
} | ||
|
||
@MainActor | ||
override func dequeueCell(in tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { | ||
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? ImageCell ?? ImageCell(reuseIdentifier: cellID) | ||
cell.update(with: self) | ||
imageView = cell.formImageView | ||
cell.formImageView.publisher(for: \.image).sink { [weak self] image in | ||
self?.image = image | ||
}.store(in: &cancellables) | ||
|
||
return cell | ||
} | ||
} | ||
|
||
private final class ImageCell: UITableViewCell { | ||
let formImageView = UIImageView() | ||
|
||
private lazy var widthConstraint: NSLayoutConstraint = formImageView.widthAnchor.constraint(equalToConstant: 300) | ||
private lazy var heightConstraint: NSLayoutConstraint = formImageView.heightAnchor.constraint(equalToConstant: 300) | ||
|
||
init(reuseIdentifier: String?) { | ||
super.init(style: .default, reuseIdentifier: reuseIdentifier) | ||
formImageView.translatesAutoresizingMaskIntoConstraints = false | ||
formImageView.backgroundColor = .tertiarySystemBackground | ||
|
||
self.contentView.addSubview(formImageView) | ||
NSLayoutConstraint.activate([ | ||
contentView.centerXAnchor.constraint(equalTo: formImageView.centerXAnchor), | ||
contentView.topAnchor.constraint(equalTo: formImageView.topAnchor), | ||
contentView.bottomAnchor.constraint(equalTo: formImageView.bottomAnchor, constant: 8), | ||
widthConstraint, | ||
heightConstraint | ||
]) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
func update(with config: ImageFormField) { | ||
widthConstraint.constant = config.size.width | ||
heightConstraint.constant = config.size.height | ||
if let image = config.image { | ||
formImageView.image = image | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
Demo/Demo/Gravatar-Demo/Common/FormFields/LabelField.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,24 @@ | ||
import UIKit | ||
|
||
final class LabelField: FormField, @unchecked Sendable { | ||
var title: String? | ||
var subtitle: String? | ||
private let cellID = "LabelCell" | ||
|
||
init(title: String? = nil, subtitle: String? = nil) { | ||
self.title = title | ||
self.subtitle = subtitle | ||
} | ||
|
||
@MainActor | ||
override func dequeueCell(in tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { | ||
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) ?? UITableViewCell(style: .subtitle, reuseIdentifier: cellID) | ||
|
||
var config = cell.defaultContentConfiguration() | ||
config.text = title | ||
config.secondaryText = subtitle | ||
cell.contentConfiguration = config | ||
|
||
return cell | ||
} | ||
} |
63 changes: 63 additions & 0 deletions
63
Demo/Demo/Gravatar-Demo/Common/FormFields/SegmentedControlField.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,63 @@ | ||
import UIKit | ||
|
||
class SegmentedControlField: FormField, @unchecked Sendable, UITextFieldDelegate { | ||
typealias OnSegmentSelected = (String, Int) -> Void | ||
|
||
var segments: [String] | ||
let actionHandler: OnSegmentSelected? | ||
|
||
@Published var selectedIndex: Int | ||
@Published var selectedSegment: String = "" | ||
|
||
private let cellID = "ImageFormCell" | ||
|
||
init(segments: [String], selectedIndex: Int = 0, action actionhandler: OnSegmentSelected? = nil) { | ||
self.segments = segments | ||
self.selectedIndex = selectedIndex | ||
self.actionHandler = actionhandler | ||
if segments.indices.contains(selectedIndex) { | ||
selectedSegment = segments[selectedIndex] | ||
} | ||
} | ||
|
||
@MainActor | ||
override func dequeueCell(in tableView: UITableView, for indexPath: IndexPath) -> UITableViewCell { | ||
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as? SegmentedControlCell ?? SegmentedControlCell(reuseIdentifier: cellID) | ||
cell.update(with: self) | ||
cell.selector.addAction(UIAction { [weak self] _ in | ||
guard let self else { return } | ||
selectedIndex = cell.selector.selectedSegmentIndex | ||
selectedSegment = segments[selectedIndex] | ||
actionHandler?(selectedSegment, selectedIndex) | ||
}, for: .valueChanged) | ||
return cell | ||
} | ||
} | ||
|
||
private final class SegmentedControlCell: UITableViewCell { | ||
let selector = UISegmentedControl() | ||
|
||
init(reuseIdentifier: String?) { | ||
super.init(style: .default, reuseIdentifier: reuseIdentifier) | ||
selector.translatesAutoresizingMaskIntoConstraints = false | ||
|
||
self.contentView.addSubview(selector) | ||
NSLayoutConstraint.activate([ | ||
contentView.centerXAnchor.constraint(equalTo: selector.centerXAnchor), | ||
contentView.topAnchor.constraint(equalTo: selector.topAnchor), | ||
contentView.bottomAnchor.constraint(equalTo: selector.bottomAnchor, constant: 8), | ||
]) | ||
} | ||
|
||
required init?(coder: NSCoder) { | ||
fatalError("init(coder:) has not been implemented") | ||
} | ||
|
||
func update(with config: SegmentedControlField) { | ||
selector.removeAllSegments() | ||
config.segments.enumerated().forEach { | ||
selector.insertSegment(withTitle: $1, at: $0, animated: true) | ||
} | ||
selector.selectedSegmentIndex = config.selectedIndex | ||
} | ||
} |
Oops, something went wrong.