refactored to have a scrolling viewcontroller
updated checkboxgroup Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
cd62738818
commit
5ed2384db3
@ -40,8 +40,16 @@
|
||||
EA3C3BB528996775000CA526 /* StoryboardInitable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB128996775000CA526 /* StoryboardInitable.swift */; };
|
||||
EA3C3BB628996775000CA526 /* MenuViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB228996775000CA526 /* MenuViewController.swift */; };
|
||||
EA3C3BB728996775000CA526 /* ToggleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3C3BB328996775000CA526 /* ToggleViewController.swift */; };
|
||||
EA89200A28B52934006B9984 /* CheckboxGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89200928B52934006B9984 /* CheckboxGroupViewController.swift */; };
|
||||
EA89201928B56DF5006B9984 /* RadioBoxGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201828B56DF5006B9984 /* RadioBoxGroupViewController.swift */; };
|
||||
EA89204628B66CE2006B9984 /* ScrollViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89203F28B66CE2006B9984 /* ScrollViewController.swift */; };
|
||||
EA89204728B66CE2006B9984 /* KeyboardFrameChangeListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204028B66CE2006B9984 /* KeyboardFrameChangeListener.swift */; };
|
||||
EA89204828B66CE2006B9984 /* ScrollViewKeyboardAvoiding.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204128B66CE2006B9984 /* ScrollViewKeyboardAvoiding.swift */; };
|
||||
EA89204928B66CE2006B9984 /* KeyboardFrameChangeListening.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204228B66CE2006B9984 /* KeyboardFrameChangeListening.swift */; };
|
||||
EA89204A28B66CE2006B9984 /* KeyboardFrameChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204328B66CE2006B9984 /* KeyboardFrameChange.swift */; };
|
||||
EA89204B28B66CE2006B9984 /* ScrollViewKeyboardAvoider.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204428B66CE2006B9984 /* ScrollViewKeyboardAvoider.swift */; };
|
||||
EA89204C28B66CE2006B9984 /* ScrollWrapperView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204528B66CE2006B9984 /* ScrollWrapperView.swift */; };
|
||||
EA89204E28B67332006B9984 /* CheckBoxGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89204D28B67332006B9984 /* CheckBoxGroupViewController.swift */; };
|
||||
EA89205128B68307006B9984 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89205028B68307006B9984 /* TextField.swift */; };
|
||||
EAB1D2C528A6B11D00DAE764 /* TestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2C428A6B11D00DAE764 /* TestViewController.swift */; };
|
||||
EAB1D2C928AAAA1D00DAE764 /* ModelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2C828AAAA1D00DAE764 /* ModelViewController.swift */; };
|
||||
EAB1D2CB28AAB9E200DAE764 /* TemplateViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB1D2CA28AAB9E200DAE764 /* TemplateViewController.swift */; };
|
||||
@ -95,8 +103,16 @@
|
||||
EA3C3BBA289968A0000CA526 /* VDSTypographyTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSTypographyTokens.xcframework; path = ../SharedFrameworks/VDSTypographyTokens.xcframework; sourceTree = "<group>"; };
|
||||
EA3C3BBB289968A0000CA526 /* VDSFormControlsTokens.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = VDSFormControlsTokens.xcframework; path = ../SharedFrameworks/VDSFormControlsTokens.xcframework; sourceTree = "<group>"; };
|
||||
EA3C3BC3289968B1000CA526 /* VDS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = VDS.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
EA89200928B52934006B9984 /* CheckboxGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxGroupViewController.swift; sourceTree = "<group>"; };
|
||||
EA89201828B56DF5006B9984 /* RadioBoxGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxGroupViewController.swift; sourceTree = "<group>"; };
|
||||
EA89203F28B66CE2006B9984 /* ScrollViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewController.swift; sourceTree = "<group>"; };
|
||||
EA89204028B66CE2006B9984 /* KeyboardFrameChangeListener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardFrameChangeListener.swift; sourceTree = "<group>"; };
|
||||
EA89204128B66CE2006B9984 /* ScrollViewKeyboardAvoiding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewKeyboardAvoiding.swift; sourceTree = "<group>"; };
|
||||
EA89204228B66CE2006B9984 /* KeyboardFrameChangeListening.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardFrameChangeListening.swift; sourceTree = "<group>"; };
|
||||
EA89204328B66CE2006B9984 /* KeyboardFrameChange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardFrameChange.swift; sourceTree = "<group>"; };
|
||||
EA89204428B66CE2006B9984 /* ScrollViewKeyboardAvoider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewKeyboardAvoider.swift; sourceTree = "<group>"; };
|
||||
EA89204528B66CE2006B9984 /* ScrollWrapperView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollWrapperView.swift; sourceTree = "<group>"; };
|
||||
EA89204D28B67332006B9984 /* CheckBoxGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxGroupViewController.swift; sourceTree = "<group>"; };
|
||||
EA89205028B68307006B9984 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = "<group>"; };
|
||||
EAB1D2C428A6B11D00DAE764 /* TestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestViewController.swift; sourceTree = "<group>"; };
|
||||
EAB1D2C828AAAA1D00DAE764 /* ModelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelViewController.swift; sourceTree = "<group>"; };
|
||||
EAB1D2CA28AAB9E200DAE764 /* TemplateViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateViewController.swift; sourceTree = "<group>"; };
|
||||
@ -156,6 +172,7 @@
|
||||
children = (
|
||||
EAF7F0C3289DA24F00B287F5 /* Supporting Files */,
|
||||
EAF7F07E28996A0700B287F5 /* Protocols */,
|
||||
EA89204F28B682F4006B9984 /* Classes */,
|
||||
EAF7F07F28996A1900B287F5 /* ViewControllers */,
|
||||
EA3C3B9C289966EF000CA526 /* AppDelegate.swift */,
|
||||
EA3C3B9E289966EF000CA526 /* SceneDelegate.swift */,
|
||||
@ -184,6 +201,28 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EA89203E28B66CE2006B9984 /* ScrollViewController */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA89203F28B66CE2006B9984 /* ScrollViewController.swift */,
|
||||
EA89204028B66CE2006B9984 /* KeyboardFrameChangeListener.swift */,
|
||||
EA89204128B66CE2006B9984 /* ScrollViewKeyboardAvoiding.swift */,
|
||||
EA89204228B66CE2006B9984 /* KeyboardFrameChangeListening.swift */,
|
||||
EA89204328B66CE2006B9984 /* KeyboardFrameChange.swift */,
|
||||
EA89204428B66CE2006B9984 /* ScrollViewKeyboardAvoider.swift */,
|
||||
EA89204528B66CE2006B9984 /* ScrollWrapperView.swift */,
|
||||
);
|
||||
path = ScrollViewController;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EA89204F28B682F4006B9984 /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA89205028B68307006B9984 /* TextField.swift */,
|
||||
);
|
||||
path = Classes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAF7F0792899698800B287F5 /* Resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -207,8 +246,9 @@
|
||||
EAF7F07F28996A1900B287F5 /* ViewControllers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA89203E28B66CE2006B9984 /* ScrollViewController */,
|
||||
EA3C3BB228996775000CA526 /* MenuViewController.swift */,
|
||||
EA89200928B52934006B9984 /* CheckboxGroupViewController.swift */,
|
||||
EA89204D28B67332006B9984 /* CheckBoxGroupViewController.swift */,
|
||||
EAF7F09B2899B92400B287F5 /* CheckboxViewController.swift */,
|
||||
EAB1D2D328AC409F00DAE764 /* LabelViewController.swift */,
|
||||
EAB1D2C828AAAA1D00DAE764 /* ModelViewController.swift */,
|
||||
@ -362,19 +402,27 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EA3C3BB728996775000CA526 /* ToggleViewController.swift in Sources */,
|
||||
EA89204C28B66CE2006B9984 /* ScrollWrapperView.swift in Sources */,
|
||||
EA89205128B68307006B9984 /* TextField.swift in Sources */,
|
||||
EA3C3BB528996775000CA526 /* StoryboardInitable.swift in Sources */,
|
||||
EA89201928B56DF5006B9984 /* RadioBoxGroupViewController.swift in Sources */,
|
||||
EA3C3BB628996775000CA526 /* MenuViewController.swift in Sources */,
|
||||
EA3C3B9D289966EF000CA526 /* AppDelegate.swift in Sources */,
|
||||
EAF7F11A28A14A0E00B287F5 /* RadioButtonViewController.swift in Sources */,
|
||||
EAB1D2CB28AAB9E200DAE764 /* TemplateViewController.swift in Sources */,
|
||||
EA89204628B66CE2006B9984 /* ScrollViewController.swift in Sources */,
|
||||
EA3C3B9F289966EF000CA526 /* SceneDelegate.swift in Sources */,
|
||||
EA89204A28B66CE2006B9984 /* KeyboardFrameChange.swift in Sources */,
|
||||
EA3C3BB428996775000CA526 /* PickerBase.swift in Sources */,
|
||||
EAB1D2C528A6B11D00DAE764 /* TestViewController.swift in Sources */,
|
||||
EAB1D2C928AAAA1D00DAE764 /* ModelViewController.swift in Sources */,
|
||||
EA89200A28B52934006B9984 /* CheckboxGroupViewController.swift in Sources */,
|
||||
EA89204728B66CE2006B9984 /* KeyboardFrameChangeListener.swift in Sources */,
|
||||
EA89204828B66CE2006B9984 /* ScrollViewKeyboardAvoiding.swift in Sources */,
|
||||
EAF7F09C2899B92400B287F5 /* CheckboxViewController.swift in Sources */,
|
||||
EA89204E28B67332006B9984 /* CheckBoxGroupViewController.swift in Sources */,
|
||||
EA89204928B66CE2006B9984 /* KeyboardFrameChangeListening.swift in Sources */,
|
||||
EAB1D2D428AC409F00DAE764 /* LabelViewController.swift in Sources */,
|
||||
EA89204B28B66CE2006B9984 /* ScrollViewKeyboardAvoider.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
32
VDSSample/Classes/TextField.swift
Normal file
32
VDSSample/Classes/TextField.swift
Normal file
@ -0,0 +1,32 @@
|
||||
//
|
||||
// TextField.swift
|
||||
// VDSSample
|
||||
//
|
||||
// Created by Matt Bruce on 8/24/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class TextField: UITextField {
|
||||
var textPadding = UIEdgeInsets(
|
||||
top: 10,
|
||||
left: 10,
|
||||
bottom: 10,
|
||||
right: 10
|
||||
)
|
||||
|
||||
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
layer.borderColor = UIColor.black.cgColor
|
||||
layer.borderWidth = 1
|
||||
let rect = super.textRect(forBounds: bounds)
|
||||
return rect.inset(by: textPadding)
|
||||
}
|
||||
|
||||
override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
layer.borderColor = UIColor.black.cgColor
|
||||
layer.borderWidth = 1
|
||||
let rect = super.editingRect(forBounds: bounds)
|
||||
return rect.inset(by: textPadding)
|
||||
}
|
||||
}
|
||||
@ -44,6 +44,28 @@ class PickerBase<EnumType: RawRepresentable>: NSObject, PickerViewable, UIPicker
|
||||
}
|
||||
}
|
||||
|
||||
class PickerSelectorView: UIStackView {
|
||||
var label = UILabel()
|
||||
var button = UIButton(type: .system).with { instance in
|
||||
instance.configuration = .filled()
|
||||
instance.setTitle("Select", for: .normal)
|
||||
}
|
||||
|
||||
init(title: String){
|
||||
super.init(frame: .zero)
|
||||
self.axis = .horizontal
|
||||
self.distribution = .fillEqually
|
||||
self.alignment = .fill
|
||||
label.text = title
|
||||
addArrangedSubview(label)
|
||||
addArrangedSubview(button)
|
||||
}
|
||||
|
||||
required init(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class SurfacePicker: PickerBase<Surface> {
|
||||
init(){
|
||||
super.init(items: [.light, .dark])
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
//
|
||||
// CheckboxViewController.swift
|
||||
// CheckBoxGroup2ViewController.swift
|
||||
// VDSSample
|
||||
//
|
||||
// Created by Matt Bruce on 8/1/22.
|
||||
// Created by Matt Bruce on 8/24/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@ -11,33 +11,69 @@ import VDS
|
||||
import VDSColorTokens
|
||||
import Combine
|
||||
|
||||
class CheckboxGroupViewController: ModelViewController<DefaultCheckboxGroupModel>, StoryboardInitable {
|
||||
|
||||
class CheckboxGroupViewController: ModelScrollViewController<DefaultCheckboxGroupModel> {
|
||||
|
||||
enum PickerType {
|
||||
case surface
|
||||
}
|
||||
static var storyboardId: String = "checkboxGroup"
|
||||
static var storyboardName: String = "Components"
|
||||
|
||||
@IBOutlet weak var checkboxContainerView: UIView!
|
||||
@IBOutlet weak var picker: UIPickerView!
|
||||
@IBOutlet weak var surfaceLabel: UILabel!
|
||||
|
||||
@IBOutlet weak var disabledSwitch: UISwitch!
|
||||
@IBOutlet weak var labelTextField: UITextField!
|
||||
@IBOutlet weak var childTextField: UITextField!
|
||||
@IBOutlet weak var showErrorSwitch: UISwitch!
|
||||
var picker = UIPickerView()
|
||||
var surfacePickerSelectorView = PickerSelectorView(title: "light")
|
||||
var disabledSwitch = UISwitch()
|
||||
var labelTextField = TextField()
|
||||
var childTextField = TextField()
|
||||
var showErrorSwitch = UISwitch()
|
||||
|
||||
|
||||
var checkboxGroup = CheckboxGroup()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
checkboxContainerView.addSubview(checkboxGroup)
|
||||
checkboxGroup.leadingAnchor.constraint(equalTo: checkboxContainerView.leadingAnchor, constant: 10).isActive = true
|
||||
checkboxGroup.bottomAnchor.constraint(equalTo: checkboxContainerView.bottomAnchor, constant: -20).isActive = true
|
||||
checkboxGroup.topAnchor.constraint(equalTo: checkboxContainerView.topAnchor, constant: 20).isActive = true
|
||||
checkboxGroup.trailingAnchor.constraint(equalTo: checkboxContainerView.trailingAnchor, constant: 10).isActive = true
|
||||
view.addGestureRecognizer(UITapGestureRecognizer(target: self.view, action: #selector(UIView.endEditing(_:))))
|
||||
addContentTopView(view: checkboxGroup)
|
||||
|
||||
addFormRow(label: "Disabled", view: disabledSwitch)
|
||||
addFormRow(label: "Surface", view: surfacePickerSelectorView)
|
||||
addFormRow(label: "Label Text", view: labelTextField)
|
||||
addFormRow(label: "Childe Text", view: childTextField)
|
||||
addFormRow(label: "Error", view: showErrorSwitch)
|
||||
|
||||
checkboxGroup
|
||||
.handlerPublisher()
|
||||
.sink { [weak self] viewModel in
|
||||
self?.model = viewModel
|
||||
}.store(in: &subscribers)
|
||||
|
||||
showErrorSwitch
|
||||
.publisher(for: .valueChanged)
|
||||
.sink { [weak self] sender in
|
||||
self?.checkboxGroup.hasError = sender.isOn
|
||||
}.store(in: &subscribers)
|
||||
|
||||
disabledSwitch
|
||||
.publisher(for: .valueChanged)
|
||||
.sink { [weak self] sender in
|
||||
self?.checkboxGroup.disabled = sender.isOn
|
||||
}.store(in: &subscribers)
|
||||
|
||||
labelTextField
|
||||
.textPublisher
|
||||
.sink { [weak self] text in
|
||||
self?.checkbox?.labelText = text
|
||||
}.store(in: &subscribers)
|
||||
|
||||
childTextField
|
||||
.textPublisher
|
||||
.sink { [weak self] text in
|
||||
self?.checkbox?.childText = text
|
||||
}.store(in: &subscribers)
|
||||
|
||||
|
||||
surfacePickerSelectorView.button
|
||||
.publisher(for: .touchUpInside)
|
||||
.sink { [weak self] _ in
|
||||
self?.pickerType = .surface
|
||||
}.store(in: &subscribers)
|
||||
|
||||
setupPicker()
|
||||
setupModel()
|
||||
}
|
||||
@ -55,15 +91,9 @@ class CheckboxGroupViewController: ModelViewController<DefaultCheckboxGroupMode
|
||||
model2.childText = "Apple iPhone 11 - 128 GB\nOtterbox Case Black\nScreen Protector"
|
||||
defaultModel.selectors = [model1, model2]
|
||||
set(with: defaultModel)
|
||||
|
||||
checkboxGroup
|
||||
.handlerPublisher()
|
||||
.sink { [weak self] viewModel in
|
||||
self?.model = viewModel
|
||||
}.store(in: &subscribers)
|
||||
|
||||
|
||||
//setup UI
|
||||
surfaceLabel.text = model.surface.rawValue
|
||||
surfacePickerSelectorView.label.text = model.surface.rawValue
|
||||
disabledSwitch.isOn = model.disabled
|
||||
labelTextField.text = model2.labelText
|
||||
childTextField.text = model1.childText
|
||||
@ -80,31 +110,8 @@ class CheckboxGroupViewController: ModelViewController<DefaultCheckboxGroupMode
|
||||
checkboxGroup.selectorViews.first
|
||||
}
|
||||
|
||||
@IBAction func disabledChanged(_ sender: UISwitch) {
|
||||
checkboxGroup.disabled = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func onLabelTextDidEnd(_ sender: UITextField) {
|
||||
checkbox?.labelText = sender.text
|
||||
sender.resignFirstResponder()
|
||||
}
|
||||
|
||||
@IBAction func onChildTextDidEnd(_ sender: UITextField) {
|
||||
checkbox?.childText = sender.text
|
||||
sender.resignFirstResponder()
|
||||
}
|
||||
|
||||
@IBAction func showErrorChanged(_ sender: UISwitch) {
|
||||
checkboxGroup.hasError = sender.isOn
|
||||
}
|
||||
|
||||
@IBAction func surfaceClick(_ sender: Any) {
|
||||
pickerType = .surface
|
||||
}
|
||||
|
||||
//Picker
|
||||
var surfacePicker = SurfacePicker()
|
||||
|
||||
var surfacePicker = SurfacePicker()
|
||||
var pickerType: PickerType = .surface {
|
||||
didSet {
|
||||
func update(object: UIPickerViewDelegate & UIPickerViewDataSource){
|
||||
@ -123,12 +130,13 @@ class CheckboxGroupViewController: ModelViewController<DefaultCheckboxGroupMode
|
||||
}
|
||||
|
||||
func setupPicker(){
|
||||
contentStackView.addArrangedSubview(picker)
|
||||
picker.isHidden = true
|
||||
surfacePicker.onPickerDidSelect = { [weak self] item in
|
||||
self?.checkboxGroup.surface = item
|
||||
self?.checkboxContainerView.backgroundColor = item.color
|
||||
self?.surfaceLabel.text = item.rawValue
|
||||
self?.contentTopView.backgroundColor = item.color
|
||||
self?.surfacePickerSelectorView.label.text = item.rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -77,3 +77,161 @@ public class ModelViewController<ModelType: Modelable>: UIViewController, ModelH
|
||||
open func updateView(viewModel: ModelType) {}
|
||||
|
||||
}
|
||||
|
||||
|
||||
public class ModelScrollViewController<ModelType: Modelable>: UIViewController, ModelHandlerable, Initable {
|
||||
deinit {
|
||||
print("\(Self.self) deinit")
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Combine Properties
|
||||
//--------------------------------------------------
|
||||
@Published public var model: ModelType = ModelType()
|
||||
public var modelPublisher: Published<ModelType>.Publisher { $model }
|
||||
public var subscribers = Set<AnyCancellable>()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Properties
|
||||
//--------------------------------------------------
|
||||
private var initialSetupPerformed = false
|
||||
|
||||
@Proxy(\.model.surface)
|
||||
open var surface: Surface
|
||||
|
||||
@Proxy(\.model.disabled)
|
||||
open var disabled: Bool
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
required public init() {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
initialSetup()
|
||||
set(with: model)
|
||||
}
|
||||
|
||||
public required init(with model: ModelType) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
initialSetup()
|
||||
set(with: model)
|
||||
}
|
||||
|
||||
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
initialSetup()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Setup
|
||||
//--------------------------------------------------
|
||||
|
||||
public func initialSetup() {
|
||||
if !initialSetupPerformed {
|
||||
initialSetupPerformed = true
|
||||
setupUpdateView()
|
||||
setup()
|
||||
}
|
||||
}
|
||||
|
||||
public var contentStackView: UIStackView = {
|
||||
return UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alignment = .fill
|
||||
$0.distribution = .fillProportionally
|
||||
$0.axis = .vertical
|
||||
}
|
||||
}()
|
||||
|
||||
public var formStackView: UIStackView = {
|
||||
return UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alignment = .fill
|
||||
$0.distribution = .fillProportionally
|
||||
$0.axis = .vertical
|
||||
$0.spacing = 10
|
||||
}
|
||||
}()
|
||||
|
||||
public var contentTopView: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}()
|
||||
|
||||
public var contentBottomView: UIView = {
|
||||
return UIView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
}
|
||||
}()
|
||||
|
||||
open override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .white
|
||||
|
||||
embed(scrollViewController)
|
||||
scrollViewController.scrollView.alwaysBounceVertical = true
|
||||
scrollViewController.contentView = contentStackView
|
||||
contentStackView.addArrangedSubview(contentTopView)
|
||||
contentStackView.addArrangedSubview(contentBottomView)
|
||||
contentBottomView.addSubview(formStackView)
|
||||
formStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
formStackView.topAnchor.constraint(equalTo: contentBottomView.topAnchor, constant: 16).isActive = true
|
||||
formStackView.leadingAnchor.constraint(equalTo: contentBottomView.leadingAnchor, constant: 16).isActive = true
|
||||
formStackView.trailingAnchor.constraint(equalTo: contentBottomView.trailingAnchor, constant: -16).isActive = true
|
||||
formStackView.bottomAnchor.constraint(equalTo: contentBottomView.bottomAnchor, constant: -16).isActive = true
|
||||
|
||||
}
|
||||
|
||||
private let scrollViewController = ScrollViewController()
|
||||
|
||||
private func embed(_ viewController: UIViewController) {
|
||||
addChild(viewController)
|
||||
view.addSubview(viewController.view)
|
||||
viewController.view.translatesAutoresizingMaskIntoConstraints = false
|
||||
viewController.view.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
|
||||
viewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
|
||||
viewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
|
||||
viewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
|
||||
viewController.didMove(toParent: self)
|
||||
}
|
||||
|
||||
open func addContentTopView(view: UIView) {
|
||||
contentTopView.addSubview(view)
|
||||
view.leadingAnchor.constraint(equalTo: contentTopView.leadingAnchor, constant: 16).isActive = true
|
||||
view.trailingAnchor.constraint(equalTo: contentTopView.trailingAnchor, constant: -16).isActive = true
|
||||
view.topAnchor.constraint(equalTo: contentTopView.topAnchor, constant: 20).isActive = true
|
||||
view.bottomAnchor.constraint(equalTo: contentTopView.bottomAnchor, constant: -20).isActive = true
|
||||
}
|
||||
|
||||
open func addFormRow(label: String, view: UIView) {
|
||||
let formRow = UIStackView().with {
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.alignment = .fill
|
||||
$0.distribution = .fillEqually
|
||||
$0.axis = .horizontal
|
||||
$0.spacing = 5
|
||||
}
|
||||
|
||||
let label = UILabel().with {
|
||||
$0.text = label
|
||||
}
|
||||
|
||||
formRow.addArrangedSubview(label)
|
||||
formRow.addArrangedSubview(view)
|
||||
|
||||
formStackView.addArrangedSubview(formRow)
|
||||
}
|
||||
|
||||
open func setup() {}
|
||||
|
||||
open func shouldUpdateView(viewModel: ModelType) -> Bool { true }
|
||||
|
||||
open func updateView(viewModel: ModelType) {}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,21 @@
|
||||
import CoreGraphics
|
||||
import Foundation
|
||||
|
||||
/// Represents keyboard frame change.
|
||||
public struct KeyboardFrameChange {
|
||||
/// Create new frame-change object.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - frame: new keyboard frame
|
||||
/// - animationDuration: change frame animation duration
|
||||
public init(frame: CGRect, animationDuration: TimeInterval) {
|
||||
self.frame = frame
|
||||
self.animationDuration = animationDuration
|
||||
}
|
||||
|
||||
/// New keyboard frame.
|
||||
public let frame: CGRect
|
||||
|
||||
/// Frame change animation duration.
|
||||
public let animationDuration: TimeInterval
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
import UIKit
|
||||
|
||||
/// `KeyboardFrameChangeListining` implementation.
|
||||
public final class KeyboardFrameChangeListener: KeyboardFrameChangeListening {
|
||||
/// Create new listener.
|
||||
///
|
||||
/// - Parameter notificationCenter: Source of keyboard frame change notifications.
|
||||
public init(notificationCenter: NotificationCenter) {
|
||||
self.notificationCenter = notificationCenter
|
||||
observe()
|
||||
}
|
||||
|
||||
// MARK: - KeyboardFrameChangeListening
|
||||
|
||||
public var keyboardFrameWillChange: ((KeyboardFrameChange) -> Void)?
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
private let notificationCenter: NotificationCenter
|
||||
private var token: NSObjectProtocol?
|
||||
|
||||
private func observe() {
|
||||
token = notificationCenter.addObserver(
|
||||
forName: UIResponder.keyboardWillChangeFrameNotification,
|
||||
object: nil,
|
||||
queue: nil,
|
||||
using: { [weak self] in self?.handle($0) }
|
||||
)
|
||||
}
|
||||
|
||||
private func handle(_ notification: Notification) {
|
||||
guard let endFrame = notification.keyboardFrameEnd,
|
||||
let animationDuration = notification.keyboardAnimationDuration else { return }
|
||||
let change = KeyboardFrameChange(frame: endFrame, animationDuration: animationDuration)
|
||||
keyboardFrameWillChange?(change)
|
||||
}
|
||||
}
|
||||
|
||||
private extension Notification {
|
||||
var keyboardFrameEnd: CGRect? {
|
||||
return userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
|
||||
}
|
||||
|
||||
var keyboardAnimationDuration: Double? {
|
||||
return userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
/// Listens for keyboard frame changes.
|
||||
public protocol KeyboardFrameChangeListening: AnyObject {
|
||||
/// Called when keyboard frame is about to change.
|
||||
var keyboardFrameWillChange: ((KeyboardFrameChange) -> Void)? { get set }
|
||||
}
|
||||
@ -0,0 +1,93 @@
|
||||
import UIKit
|
||||
|
||||
/// Scroll View Controller.
|
||||
public class ScrollViewController: UIViewController, UIScrollViewDelegate {
|
||||
/// Animates using provided duration and closure.
|
||||
public typealias Animator = (TimeInterval, @escaping () -> Void) -> Void
|
||||
|
||||
/// Create new instance.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - keyboardFrameChangeListener: Used to observe keybaord frame changes.
|
||||
/// - scrollViewKeyboardAvoider: Used to apply keyboard-avoiding insets to `UIScrollView`.
|
||||
/// - wrapperViewFactory: Used to create `ScrollWrapperView`.
|
||||
/// - animator: Closure used to animate layout changes.
|
||||
public init(keyboardFrameChangeListener: KeyboardFrameChangeListening = KeyboardFrameChangeListener(notificationCenter: NotificationCenter.default),
|
||||
scrollViewKeyboardAvoider: ScrollViewKeyboardAvoiding = ScrollViewKeyboardAvoider(animator: { UIView.animate(withDuration: $0, animations: $1) }),
|
||||
wrapperViewFactory: @escaping () -> ScrollWrapperView = { ScrollWrapperView() },
|
||||
animator: @escaping Animator = { UIView.animate(withDuration: $0, animations: $1) }) {
|
||||
self.keyboardFrameChangeListener = keyboardFrameChangeListener
|
||||
self.scrollViewKeyboardAvoider = scrollViewKeyboardAvoider
|
||||
self.createWrapperView = wrapperViewFactory
|
||||
self.animate = animator
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
/// Does nothing, this class is designed to be used programmatically.
|
||||
required public init?(coder aDecoder: NSCoder) { nil }
|
||||
|
||||
// MARK: - View
|
||||
|
||||
override public func loadView() {
|
||||
view = createWrapperView()
|
||||
}
|
||||
|
||||
override public func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
wrapperView.scrollView.delegate = self
|
||||
keyboardFrameChangeListener.keyboardFrameWillChange = { [unowned self] change in
|
||||
self.scrollViewKeyboardAvoider.handleKeyboardFrameChange(
|
||||
change.frame,
|
||||
animationDuration: change.animationDuration,
|
||||
for: self.wrapperView.scrollView
|
||||
)
|
||||
self.updateVisibleContentInset(scrollView: self.wrapperView.scrollView)
|
||||
self.animate(change.animationDuration) {
|
||||
self.wrapperView.layoutIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override func viewDidLayoutSubviews() {
|
||||
super.viewDidLayoutSubviews()
|
||||
updateVisibleContentInset(scrollView: wrapperView.scrollView)
|
||||
}
|
||||
|
||||
/// Contained `UIScrollView`.
|
||||
public var scrollView: UIScrollView {
|
||||
return wrapperView.scrollView
|
||||
}
|
||||
|
||||
/// Scrollable content view.
|
||||
public var contentView: UIView? {
|
||||
get { return wrapperView.contentView }
|
||||
set { wrapperView.contentView = newValue }
|
||||
}
|
||||
|
||||
/// Main view of this view controller (non-scrollable).
|
||||
public var wrapperView: ScrollWrapperView! {
|
||||
return view as? ScrollWrapperView
|
||||
}
|
||||
|
||||
// MARK: - UIScrollViewDelegate
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
|
||||
updateVisibleContentInset(scrollView: scrollView)
|
||||
}
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
private let keyboardFrameChangeListener: KeyboardFrameChangeListening
|
||||
private let scrollViewKeyboardAvoider: ScrollViewKeyboardAvoiding
|
||||
private let createWrapperView: () -> ScrollWrapperView
|
||||
private let animate: Animator
|
||||
|
||||
private func updateVisibleContentInset(scrollView: UIScrollView) {
|
||||
if #available(iOS 11.0, *) {
|
||||
wrapperView.visibleContentInsets = scrollView.adjustedContentInset
|
||||
} else {
|
||||
wrapperView.visibleContentInsets = scrollView.contentInset
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import UIKit
|
||||
|
||||
/// `ScrollViewKeyboardAvoiding` implementation.
|
||||
public final class ScrollViewKeyboardAvoider: ScrollViewKeyboardAvoiding {
|
||||
/// Animates using provided duration and closure
|
||||
public typealias Animator = (TimeInterval, @escaping () -> Void) -> Void
|
||||
|
||||
/// Create new avoider
|
||||
///
|
||||
/// - Parameter animator: used to perform animations
|
||||
public init(animator: @escaping Animator) {
|
||||
self.animate = animator
|
||||
}
|
||||
|
||||
// MARK: - ScrollViewKeyboardAvoiding
|
||||
|
||||
public func handleKeyboardFrameChange(
|
||||
_ frame: CGRect,
|
||||
animationDuration: TimeInterval,
|
||||
for scrollView: UIScrollView
|
||||
) {
|
||||
guard let superview = scrollView.superview else { return }
|
||||
let keyboardFrame = superview.convert(frame, from: nil)
|
||||
var insets = scrollView.contentInset
|
||||
let bottomCoverage = scrollView.frame.maxY - keyboardFrame.minY
|
||||
let safeAreaInsets: UIEdgeInsets
|
||||
if #available(iOS 11.0, *) {
|
||||
safeAreaInsets = scrollView.safeAreaInsets
|
||||
} else {
|
||||
safeAreaInsets = .zero
|
||||
}
|
||||
insets.bottom = max(0, bottomCoverage - safeAreaInsets.bottom)
|
||||
animate(animationDuration) {
|
||||
scrollView.contentInset = insets
|
||||
scrollView.scrollIndicatorInsets = insets
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
private let animate: Animator
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import UIKit
|
||||
|
||||
/// Adjusts insets of `UIScrollView` so the keyboard does not cover content.
|
||||
public protocol ScrollViewKeyboardAvoiding {
|
||||
/// Handle keyboard frame change.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - frame: New frame of the keyboard.
|
||||
/// - animationDuration: Frame change animation duration.
|
||||
/// - scrollView: Target `UIScrollView`.
|
||||
func handleKeyboardFrameChange(_ frame: CGRect, animationDuration: TimeInterval, for scrollView: UIScrollView)
|
||||
}
|
||||
@ -0,0 +1,191 @@
|
||||
import UIKit
|
||||
|
||||
/// `UIScrollView` wrapper that allows configuring how the scrollable content is laid out.
|
||||
public class ScrollWrapperView: UIView {
|
||||
/// Create `UIScrollView` wrapper view.
|
||||
public init() {
|
||||
scrollView = UIScrollView(frame: .zero)
|
||||
super.init(frame: .zero)
|
||||
scrollView.keyboardDismissMode = .interactive
|
||||
addSubview(scrollView)
|
||||
scrollView.addSubview(contentWrapperView)
|
||||
setupLayout()
|
||||
}
|
||||
|
||||
/// Does nothing, this class is designed to be used programmatically.
|
||||
required public init?(coder aDecoder: NSCoder) { nil }
|
||||
|
||||
// MARK: - Subviews
|
||||
|
||||
/// Wrapped `UIScrollView`.
|
||||
public let scrollView: UIScrollView
|
||||
|
||||
/// Scrollable content view.
|
||||
public var contentView: UIView? {
|
||||
didSet {
|
||||
oldValue?.removeFromSuperview()
|
||||
contentViewTopEqualSuper = nil
|
||||
contentViewTopGreaterThanSuper = nil
|
||||
contentViewLeft = nil
|
||||
contentViewRight = nil
|
||||
contentViewBottom = nil
|
||||
if let newValue = contentView {
|
||||
contentWrapperView.addSubview(newValue)
|
||||
setupLayout(contentView: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let contentWrapperView = UIView()
|
||||
|
||||
// MARK: - Layout configuration
|
||||
|
||||
/// If `true`, `contentView` will be stretched to fill visible area.
|
||||
///
|
||||
/// Default is `true`.
|
||||
public var contentViewStretching = true {
|
||||
didSet { contentWrapperHeight.isActive = contentViewStretching }
|
||||
}
|
||||
|
||||
/// If `true` the content view will be aligned to the bottom of scrollable area.
|
||||
///
|
||||
/// Default is `false`.
|
||||
///
|
||||
/// If the `contentViewStretching` is set to `false` this property makes no changes to the alignemnt.
|
||||
public var alignContentToBottom = false {
|
||||
didSet {
|
||||
contentViewTopGreaterThanSuper?.isActive = alignContentToBottom == true
|
||||
contentViewTopEqualSuper?.isActive = alignContentToBottom == false
|
||||
}
|
||||
}
|
||||
|
||||
/// Scrollable content insets.
|
||||
///
|
||||
/// Default is `.zero` which means no insets.
|
||||
public var contentInsets: UIEdgeInsets = .zero {
|
||||
didSet {
|
||||
contentViewTopEqualSuper?.constant = contentInsets.top
|
||||
contentViewTopGreaterThanSuper?.constant = contentInsets.top
|
||||
contentViewLeft?.constant = contentInsets.left
|
||||
contentViewRight?.constant = -contentInsets.right
|
||||
contentViewBottom?.constant = -contentInsets.bottom
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Touch handling configuration
|
||||
|
||||
/// If `true` touches outside the `contentView` will be handled and allow scrolling.
|
||||
///
|
||||
/// Default is `true`.
|
||||
public var handlesTouchesOutsideContent = true
|
||||
|
||||
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if handlesTouchesOutsideContent {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
if let contentView = contentView, contentView.bounds.contains(convert(point, to: contentView)) {
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MARK: - Internals
|
||||
|
||||
var visibleContentInsets: UIEdgeInsets {
|
||||
get {
|
||||
UIEdgeInsets(
|
||||
top: visibleContentLayoutGuideTop.constant,
|
||||
left: visibleContentLayoutGuideLeft.constant,
|
||||
bottom: -visibleContentLayoutGuideBottom.constant,
|
||||
right: -visibleContentLayoutGuideRight.constant
|
||||
)
|
||||
}
|
||||
set {
|
||||
visibleContentLayoutGuideTop.constant = newValue.top
|
||||
visibleContentLayoutGuideLeft.constant = newValue.left
|
||||
visibleContentLayoutGuideRight.constant = -newValue.right
|
||||
visibleContentLayoutGuideBottom.constant = -newValue.bottom
|
||||
}
|
||||
}
|
||||
|
||||
private let visibleContentLayoutGuide = UILayoutGuide()
|
||||
private var visibleContentLayoutGuideTop: NSLayoutConstraint!
|
||||
private var visibleContentLayoutGuideLeft: NSLayoutConstraint!
|
||||
private var visibleContentLayoutGuideRight: NSLayoutConstraint!
|
||||
private var visibleContentLayoutGuideBottom: NSLayoutConstraint!
|
||||
private var contentWrapperHeight: NSLayoutConstraint!
|
||||
private var contentViewTopEqualSuper: NSLayoutConstraint?
|
||||
private var contentViewTopGreaterThanSuper: NSLayoutConstraint?
|
||||
private var contentViewLeft: NSLayoutConstraint?
|
||||
private var contentViewRight: NSLayoutConstraint?
|
||||
private var contentViewBottom: NSLayoutConstraint?
|
||||
|
||||
private func setupLayout() {
|
||||
setupVisibleContentLayoutGuide()
|
||||
setupScrollViewLayout()
|
||||
setupContentWrapperViewLayout()
|
||||
}
|
||||
|
||||
private func setupScrollViewLayout() {
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.topAnchor.constraint(equalTo: topAnchor).isActive = true
|
||||
scrollView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
|
||||
scrollView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
|
||||
scrollView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
|
||||
}
|
||||
|
||||
private func setupContentWrapperViewLayout() {
|
||||
contentWrapperView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentWrapperView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
|
||||
contentWrapperView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
|
||||
contentWrapperView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
|
||||
contentWrapperView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
|
||||
contentWrapperView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
|
||||
contentWrapperHeight = contentWrapperView.heightAnchor.constraint(
|
||||
greaterThanOrEqualTo: visibleContentLayoutGuide.heightAnchor
|
||||
)
|
||||
contentWrapperHeight.isActive = contentViewStretching
|
||||
}
|
||||
|
||||
private func setupVisibleContentLayoutGuide() {
|
||||
addLayoutGuide(visibleContentLayoutGuide)
|
||||
|
||||
visibleContentLayoutGuideTop = visibleContentLayoutGuide.topAnchor.constraint(equalTo: topAnchor)
|
||||
visibleContentLayoutGuideTop.isActive = true
|
||||
|
||||
visibleContentLayoutGuideLeft = visibleContentLayoutGuide.leftAnchor.constraint(equalTo: leftAnchor)
|
||||
visibleContentLayoutGuideLeft.isActive = true
|
||||
|
||||
visibleContentLayoutGuideRight = visibleContentLayoutGuide.rightAnchor.constraint(equalTo: rightAnchor)
|
||||
visibleContentLayoutGuideRight.priority = .defaultHigh
|
||||
visibleContentLayoutGuideRight.isActive = true
|
||||
|
||||
visibleContentLayoutGuideBottom = visibleContentLayoutGuide.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
visibleContentLayoutGuideBottom.priority = .defaultHigh
|
||||
visibleContentLayoutGuideBottom.isActive = true
|
||||
}
|
||||
|
||||
private func setupLayout(contentView view: UIView) {
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
contentViewTopEqualSuper = view.topAnchor.constraint(equalTo: contentWrapperView.topAnchor)
|
||||
contentViewTopEqualSuper?.constant = contentInsets.top
|
||||
contentViewTopEqualSuper?.isActive = alignContentToBottom == false
|
||||
|
||||
contentViewTopGreaterThanSuper = view.topAnchor.constraint(greaterThanOrEqualTo: contentWrapperView.topAnchor)
|
||||
contentViewTopGreaterThanSuper?.constant = contentInsets.top
|
||||
contentViewTopGreaterThanSuper?.isActive = alignContentToBottom == true
|
||||
|
||||
contentViewLeft = view.leftAnchor.constraint(equalTo: contentWrapperView.leftAnchor)
|
||||
contentViewLeft?.constant = contentInsets.left
|
||||
contentViewLeft?.isActive = true
|
||||
|
||||
contentViewRight = view.rightAnchor.constraint(equalTo: contentWrapperView.rightAnchor)
|
||||
contentViewRight?.constant = -contentInsets.right
|
||||
contentViewRight?.isActive = true
|
||||
|
||||
contentViewBottom = view.bottomAnchor.constraint(equalTo: contentWrapperView.bottomAnchor)
|
||||
contentViewBottom?.constant = -contentInsets.bottom
|
||||
contentViewBottom?.isActive = true
|
||||
}
|
||||
}
|
||||
@ -128,26 +128,3 @@ class UserNameView: UIControl {
|
||||
}.store(in: &subscriptions)
|
||||
}
|
||||
}
|
||||
|
||||
class TextField: UITextField {
|
||||
var textPadding = UIEdgeInsets(
|
||||
top: 10,
|
||||
left: 20,
|
||||
bottom: 10,
|
||||
right: 20
|
||||
)
|
||||
|
||||
override func textRect(forBounds bounds: CGRect) -> CGRect {
|
||||
layer.borderColor = UIColor.black.cgColor
|
||||
layer.borderWidth = 1
|
||||
let rect = super.textRect(forBounds: bounds)
|
||||
return rect.inset(by: textPadding)
|
||||
}
|
||||
|
||||
override func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
layer.borderColor = UIColor.black.cgColor
|
||||
layer.borderWidth = 1
|
||||
let rect = super.editingRect(forBounds: bounds)
|
||||
return rect.inset(by: textPadding)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user