| // Copyright 2016-present the Material Components for iOS authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // swiftlint:disable function_body_length |
| |
| import MaterialComponents.MaterialTextFields |
| |
| final class TextFieldManualLayoutLegacySwiftExample: UIViewController { |
| |
| private enum LayoutConstants { |
| static let largeMargin: CGFloat = 16 |
| static let smallMargin: CGFloat = 8 |
| |
| static let floatingHeight: CGFloat = 84 |
| static let defaultHeight: CGFloat = 62 |
| |
| static let stateWidth: CGFloat = 80 |
| } |
| |
| let scrollView = UIScrollView() |
| |
| let name: MDCTextField = { |
| let name = MDCTextField() |
| name.autocapitalizationType = .words |
| return name |
| }() |
| |
| let address: MDCTextField = { |
| let address = MDCTextField() |
| address.autocapitalizationType = .words |
| return address |
| }() |
| |
| let city: MDCTextField = { |
| let city = MDCTextField() |
| city.autocapitalizationType = .words |
| return city |
| }() |
| let cityController: MDCTextInputControllerLegacyDefault |
| |
| let state: MDCTextField = { |
| let state = MDCTextField() |
| state.autocapitalizationType = .allCharacters |
| return state |
| }() |
| |
| let zip: MDCTextField = { |
| let zip = MDCTextField() |
| return zip |
| }() |
| let zipController: MDCTextInputControllerLegacyDefault |
| |
| let phone: MDCTextField = { |
| let phone = MDCTextField() |
| return phone |
| }() |
| |
| let stateZip = UIView() |
| |
| var allTextFieldControllers = [MDCTextInputControllerLegacyDefault]() |
| |
| override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { |
| cityController = MDCTextInputControllerLegacyDefault(textInput: city) |
| zipController = MDCTextInputControllerLegacyDefault(textInput: zip) |
| super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) |
| } |
| |
| required init?(coder aDecoder: NSCoder) { |
| fatalError("init(coder:) has not been implemented") |
| } |
| |
| deinit { |
| NotificationCenter.default.removeObserver(self) |
| } |
| |
| override func viewDidLoad() { |
| super.viewDidLoad() |
| view.backgroundColor = UIColor(white:0.97, alpha: 1.0) |
| |
| title = "Legacy Manual Text Fields" |
| |
| setupScrollView() |
| setupTextFields() |
| |
| updateLayout() |
| |
| registerKeyboardNotifications() |
| addGestureRecognizer() |
| |
| let styleButton = UIBarButtonItem(title: "Style", |
| style: .plain, |
| target: self, |
| action: #selector(buttonDidTouch(sender: ))) |
| self.navigationItem.rightBarButtonItem = styleButton |
| } |
| |
| override func viewWillAppear(_ animated: Bool) { |
| super.viewWillAppear(animated) |
| scrollView.frame = view.bounds |
| } |
| |
| func setupTextFields() { |
| scrollView.addSubview(name) |
| let nameController = MDCTextInputControllerLegacyDefault(textInput: name) |
| name.delegate = self |
| nameController.placeholderText = "Name" |
| allTextFieldControllers.append(nameController) |
| |
| scrollView.addSubview(address) |
| let addressController = MDCTextInputControllerLegacyDefault(textInput: address) |
| address.delegate = self |
| addressController.placeholderText = "Address" |
| allTextFieldControllers.append(addressController) |
| |
| scrollView.addSubview(city) |
| city.delegate = self |
| cityController.placeholderText = "City" |
| allTextFieldControllers.append(cityController) |
| |
| scrollView.addSubview(stateZip) |
| |
| stateZip.addSubview(state) |
| let stateController = MDCTextInputControllerLegacyDefault(textInput: state) |
| state.delegate = self |
| stateController.placeholderText = "State" |
| allTextFieldControllers.append(stateController) |
| |
| stateZip.addSubview(zip) |
| zip.delegate = self |
| zipController.placeholderText = "Zip Code" |
| zipController.helperText = "XXXXX" |
| allTextFieldControllers.append(zipController) |
| |
| scrollView.addSubview(phone) |
| let phoneController = MDCTextInputControllerLegacyDefault(textInput: phone) |
| phone.delegate = self |
| phoneController.placeholderText = "Phone Number" |
| allTextFieldControllers.append(phoneController) |
| |
| var tag = 0 |
| for controller in allTextFieldControllers { |
| guard let textField = controller.textInput as? MDCTextField else { continue } |
| textField.tag = tag |
| tag += 1 |
| } |
| |
| } |
| |
| func setupScrollView() { |
| view.addSubview(scrollView) |
| |
| scrollView.contentSize = |
| CGSize(width: scrollView.bounds.width - 2 * LayoutConstants.largeMargin, |
| height: 500.0) |
| } |
| |
| func addGestureRecognizer() { |
| let tapRecognizer = UITapGestureRecognizer(target: self, |
| action: #selector(tapDidTouch(sender: ))) |
| self.scrollView.addGestureRecognizer(tapRecognizer) |
| } |
| |
| func updateLayout() { |
| let commonWidth = view.bounds.width - 2 * LayoutConstants.largeMargin |
| var height = LayoutConstants.floatingHeight |
| if let controller = allTextFieldControllers.first { |
| height = controller.isFloatingEnabled ? |
| LayoutConstants.floatingHeight : LayoutConstants.defaultHeight |
| } |
| |
| name.frame = CGRect(x: LayoutConstants.largeMargin, |
| y: LayoutConstants.smallMargin, |
| width: commonWidth, |
| height: height) |
| |
| address.frame = CGRect(x: LayoutConstants.largeMargin, |
| y: name.frame.minY + height + LayoutConstants.smallMargin, |
| width: commonWidth, |
| height: height) |
| |
| city.frame = CGRect(x: LayoutConstants.largeMargin, |
| y: address.frame.minY + height + LayoutConstants.smallMargin, |
| width: commonWidth, |
| height: height) |
| |
| stateZip.frame = CGRect(x: LayoutConstants.largeMargin, |
| y: city.frame.minY + height + LayoutConstants.smallMargin, |
| width: commonWidth, |
| height: height) |
| |
| state.frame = CGRect(x: 0, |
| y: 0, |
| width: LayoutConstants.stateWidth, |
| height: height) |
| |
| zip.frame = CGRect(x: LayoutConstants.stateWidth + LayoutConstants.smallMargin, |
| y: 0, |
| width: stateZip.bounds.width - LayoutConstants.stateWidth - |
| LayoutConstants.smallMargin, |
| height: height) |
| |
| phone.frame = CGRect(x: LayoutConstants.largeMargin, |
| y: stateZip.frame.minY + height + LayoutConstants.smallMargin, |
| width: commonWidth, |
| height: height) |
| } |
| |
| // MARK: - Actions |
| |
| @objc func tapDidTouch(sender: Any) { |
| self.view.endEditing(true) |
| } |
| |
| @objc func buttonDidTouch(sender: Any) { |
| let alert = UIAlertController(title: "Floating Enabled", |
| message: nil, |
| preferredStyle: .actionSheet) |
| let defaultAction = UIAlertAction(title: "Default (Yes)", style: .default) { _ in |
| self.allTextFieldControllers.forEach({ (controller) in |
| controller.isFloatingEnabled = true |
| }) |
| self.updateLayout() |
| } |
| alert.addAction(defaultAction) |
| let floatingAction = UIAlertAction(title: "No", style: .default) { _ in |
| self.allTextFieldControllers.forEach({ (controller) in |
| controller.isFloatingEnabled = false |
| }) |
| self.updateLayout() |
| } |
| alert.addAction(floatingAction) |
| present(alert, animated: true, completion: nil) |
| } |
| } |
| |
| extension TextFieldManualLayoutLegacySwiftExample: UITextFieldDelegate { |
| func textField(_ textField: UITextField, |
| shouldChangeCharactersIn range: NSRange, |
| replacementString string: String) -> Bool { |
| guard let rawText = textField.text else { |
| return true |
| } |
| |
| let fullString = NSString(string: rawText).replacingCharacters(in: range, with: string) |
| |
| if textField == zip { |
| if let range = fullString.rangeOfCharacter(from: CharacterSet.letters), |
| String(fullString[range]).characterCount > 0 { |
| zipController.setErrorText("Error: Zip can only contain numbers", |
| errorAccessibilityValue: nil) |
| } else if fullString.characterCount > 5 { |
| zipController.setErrorText("Error: Zip can only contain five digits", |
| errorAccessibilityValue: nil) |
| } else { |
| zipController.setErrorText(nil, errorAccessibilityValue: nil) |
| } |
| } else if textField == city { |
| if let range = fullString.rangeOfCharacter(from: CharacterSet.decimalDigits), |
| String(fullString[range]).characterCount > 0 { |
| cityController.setErrorText("Error: City can only contain letters", |
| errorAccessibilityValue: nil) |
| } else { |
| cityController.setErrorText(nil, errorAccessibilityValue: nil) |
| } |
| } |
| return true |
| } |
| |
| func textFieldShouldReturn(_ textField: UITextField) -> Bool { |
| let index = textField.tag |
| if index + 1 < allTextFieldControllers.count, |
| let nextField = allTextFieldControllers[index + 1].textInput { |
| nextField.becomeFirstResponder() |
| } else { |
| textField.resignFirstResponder() |
| } |
| |
| return false |
| } |
| } |
| |
| // MARK: - Keyboard Handling |
| |
| extension TextFieldManualLayoutLegacySwiftExample { |
| func registerKeyboardNotifications() { |
| let notificationCenter = NotificationCenter.default |
| notificationCenter.addObserver( |
| self, |
| selector: #selector(keyboardWillShow(notif:)), |
| name: UIResponder.keyboardWillShowNotification, |
| object: nil) |
| notificationCenter.addObserver( |
| self, |
| selector: #selector(keyboardWillShow(notif:)), |
| name: UIResponder.keyboardWillChangeFrameNotification, |
| object: nil) |
| notificationCenter.addObserver( |
| self, |
| selector: #selector(keyboardWillHide(notif:)), |
| name: UIResponder.keyboardWillHideNotification, |
| object: nil) |
| } |
| |
| @objc func keyboardWillShow(notif: Notification) { |
| guard let frame = notif.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { |
| return |
| } |
| scrollView.contentInset = UIEdgeInsets(top: 0.0, |
| left: 0.0, |
| bottom: frame.height, |
| right: 0.0) |
| } |
| |
| @objc func keyboardWillHide(notif: Notification) { |
| scrollView.contentInset = UIEdgeInsets() |
| } |
| } |
| |
| // MARK: - Status Bar Style |
| |
| extension TextFieldManualLayoutLegacySwiftExample { |
| override var preferredStatusBarStyle: UIStatusBarStyle { |
| return .lightContent |
| } |
| } |
| |
| // MARK: - CatalogByConvention |
| extension TextFieldManualLayoutLegacySwiftExample { |
| |
| @objc class func catalogMetadata() -> [String: Any] { |
| return [ |
| "breadcrumbs": ["Text Field", "[Legacy] Manual Layout (Swift)"], |
| "primaryDemo": false, |
| "presentable": false, |
| ] |
| } |
| } |