mirror of
https://github.com/material-components/material-components-ios.git
synced 2026-01-21 12:33:23 +08:00
421 lines
16 KiB
Swift
421 lines
16 KiB
Swift
// 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_Theming
|
|
|
|
final class TextFieldOutlinedSwiftExample: UIViewController {
|
|
|
|
let scrollView = UIScrollView()
|
|
@objc var containerScheme = MDCContainerScheme()
|
|
|
|
let name: MDCTextField = {
|
|
let name = MDCTextField()
|
|
name.translatesAutoresizingMaskIntoConstraints = false
|
|
name.autocapitalizationType = .words
|
|
name.backgroundColor = .white
|
|
return name
|
|
}()
|
|
|
|
let address: MDCTextField = {
|
|
let address = MDCTextField()
|
|
address.translatesAutoresizingMaskIntoConstraints = false
|
|
address.autocapitalizationType = .words
|
|
address.backgroundColor = .white
|
|
return address
|
|
}()
|
|
|
|
let city: MDCTextField = {
|
|
let city = MDCTextField()
|
|
city.translatesAutoresizingMaskIntoConstraints = false
|
|
city.autocapitalizationType = .words
|
|
city.backgroundColor = .white
|
|
return city
|
|
}()
|
|
let cityController: MDCTextInputControllerOutlined
|
|
|
|
let state: MDCTextField = {
|
|
let state = MDCTextField()
|
|
state.translatesAutoresizingMaskIntoConstraints = false
|
|
state.autocapitalizationType = .allCharacters
|
|
state.backgroundColor = .white
|
|
return state
|
|
}()
|
|
let stateController: MDCTextInputControllerOutlined
|
|
|
|
let zip: MDCTextField = {
|
|
let zip = MDCTextField()
|
|
zip.translatesAutoresizingMaskIntoConstraints = false
|
|
zip.backgroundColor = .white
|
|
return zip
|
|
}()
|
|
let zipController: MDCTextInputControllerOutlined
|
|
|
|
let phone: MDCTextField = {
|
|
let phone = MDCTextField()
|
|
phone.translatesAutoresizingMaskIntoConstraints = false
|
|
phone.backgroundColor = .white
|
|
return phone
|
|
}()
|
|
|
|
let message: MDCMultilineTextField = {
|
|
let message = MDCMultilineTextField()
|
|
message.translatesAutoresizingMaskIntoConstraints = false
|
|
message.backgroundColor = .white
|
|
return message
|
|
}()
|
|
|
|
var allTextFieldControllers = [MDCTextInputControllerFloatingPlaceholder]()
|
|
|
|
let leadingImage: UIImage = {
|
|
return UIImage.init(named: "ic_search",
|
|
in: Bundle(for: TextFieldOutlinedSwiftExample.self),
|
|
compatibleWith: nil) ?? UIImage()
|
|
}()
|
|
|
|
let trailingImage: UIImage = {
|
|
return UIImage.init(named: "ic_done",
|
|
in: Bundle(for: TextFieldOutlinedSwiftExample.self),
|
|
compatibleWith: nil) ?? UIImage()
|
|
}()
|
|
|
|
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
|
|
cityController = MDCTextInputControllerOutlined(textInput: city)
|
|
stateController = MDCTextInputControllerOutlined(textInput: state)
|
|
zipController = MDCTextInputControllerOutlined(textInput: zip)
|
|
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
view.backgroundColor = .white
|
|
|
|
title = "Outlined Text Fields"
|
|
|
|
setupScrollView()
|
|
setupTextFields()
|
|
|
|
registerKeyboardNotifications()
|
|
addGestureRecognizer()
|
|
}
|
|
|
|
func setupTextFields() {
|
|
scrollView.addSubview(name)
|
|
let nameController = MDCTextInputControllerOutlined(textInput: name)
|
|
name.delegate = self
|
|
name.text = "Grace Hopper"
|
|
name.leadingView = UIImageView(image: leadingImage)
|
|
name.leadingViewMode = .always
|
|
name.trailingView = UIImageView(image: trailingImage)
|
|
name.trailingViewMode = .always
|
|
nameController.placeholderText = "Name"
|
|
nameController.helperText = "First and Last"
|
|
allTextFieldControllers.append(nameController)
|
|
|
|
scrollView.addSubview(address)
|
|
let addressController = MDCTextInputControllerOutlined(textInput: address)
|
|
address.delegate = self
|
|
addressController.placeholderText = "Address"
|
|
allTextFieldControllers.append(addressController)
|
|
|
|
scrollView.addSubview(city)
|
|
city.delegate = self
|
|
cityController.placeholderText = "City"
|
|
allTextFieldControllers.append(cityController)
|
|
|
|
// In iOS 9+, you could accomplish this with a UILayoutGuide.
|
|
// TODO: (larche) add iOS version specific implementations
|
|
let stateZip = UIView()
|
|
stateZip.translatesAutoresizingMaskIntoConstraints = false
|
|
scrollView.addSubview(stateZip)
|
|
|
|
stateZip.addSubview(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 = MDCTextInputControllerOutlined(textInput: phone)
|
|
phone.delegate = self
|
|
phoneController.placeholderText = "Phone Number"
|
|
allTextFieldControllers.append(phoneController)
|
|
|
|
scrollView.addSubview(message)
|
|
let messageController = MDCTextInputControllerOutlinedTextArea(textInput: message)
|
|
message.textView?.delegate = self
|
|
message.text = """
|
|
This is where you could put a multi-line message like an email.
|
|
|
|
It can even handle new lines.
|
|
"""
|
|
messageController.placeholderText = "Message"
|
|
allTextFieldControllers.append(messageController)
|
|
|
|
messageController.characterCountMax = 150
|
|
|
|
var tag = 0
|
|
for controller in allTextFieldControllers {
|
|
guard let textField = controller.textInput as? MDCTextField else { continue }
|
|
style(textInputController: controller);
|
|
textField.tag = tag
|
|
tag += 1
|
|
}
|
|
|
|
let views = [ "name": name,
|
|
"address": address,
|
|
"city": city,
|
|
"stateZip": stateZip,
|
|
"phone": phone,
|
|
"message": message ]
|
|
var constraints = NSLayoutConstraint.constraints(withVisualFormat:
|
|
"V:[name]-[address]-[city]-[stateZip]-[phone]-[message]",
|
|
options: [.alignAllLeading, .alignAllTrailing],
|
|
metrics: nil,
|
|
views: views)
|
|
|
|
constraints += [NSLayoutConstraint(item: name,
|
|
attribute: .leading,
|
|
relatedBy: .equal,
|
|
toItem: view,
|
|
attribute: .leadingMargin,
|
|
multiplier: 1,
|
|
constant: 0)]
|
|
constraints += [NSLayoutConstraint(item: name,
|
|
attribute: .trailing,
|
|
relatedBy: .equal,
|
|
toItem: view,
|
|
attribute: .trailingMargin,
|
|
multiplier: 1,
|
|
constant: 0)]
|
|
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:[name]|",
|
|
options: [],
|
|
metrics: nil,
|
|
views: views)
|
|
|
|
if #available(iOS 11.0, *) {
|
|
constraints += [NSLayoutConstraint(item: name,
|
|
attribute: .top,
|
|
relatedBy: .equal,
|
|
toItem: scrollView.contentLayoutGuide,
|
|
attribute: .top,
|
|
multiplier: 1,
|
|
constant: 20),
|
|
NSLayoutConstraint(item: message,
|
|
attribute: .bottom,
|
|
relatedBy: .equal,
|
|
toItem: scrollView.contentLayoutGuide,
|
|
attribute: .bottomMargin,
|
|
multiplier: 1,
|
|
constant: -20)]
|
|
} else {
|
|
constraints += [NSLayoutConstraint(item: name,
|
|
attribute: .top,
|
|
relatedBy: .equal,
|
|
toItem: scrollView,
|
|
attribute: .top,
|
|
multiplier: 1,
|
|
constant: 20),
|
|
NSLayoutConstraint(item: message,
|
|
attribute: .bottom,
|
|
relatedBy: .equal,
|
|
toItem: scrollView,
|
|
attribute: .bottomMargin,
|
|
multiplier: 1,
|
|
constant: -20)]
|
|
}
|
|
|
|
let stateZipViews = [ "state": state, "zip": zip ]
|
|
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[state(80)]-[zip]|",
|
|
options: [.alignAllTop],
|
|
metrics: nil,
|
|
views: stateZipViews)
|
|
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[state]|",
|
|
options: [],
|
|
metrics: nil,
|
|
views: stateZipViews)
|
|
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|[zip]|",
|
|
options: [],
|
|
metrics: nil,
|
|
views: stateZipViews)
|
|
|
|
NSLayoutConstraint.activate(constraints)
|
|
}
|
|
|
|
func setupScrollView() {
|
|
view.addSubview(scrollView)
|
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(
|
|
withVisualFormat: "V:|[topGuide]-[scrollView]|",
|
|
options: [],
|
|
metrics: nil,
|
|
views: ["scrollView": scrollView, "topGuide": topLayoutGuide]))
|
|
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|[scrollView]|",
|
|
options: [],
|
|
metrics: nil,
|
|
views: ["scrollView": scrollView]))
|
|
let marginOffset: CGFloat = 16
|
|
let margins = UIEdgeInsets(top: 0, left: marginOffset, bottom: 0, right: marginOffset)
|
|
|
|
scrollView.layoutMargins = margins
|
|
}
|
|
|
|
func style(textInputController : MDCTextInputController) {
|
|
guard let outlinedController =
|
|
textInputController as? MDCTextInputControllerOutlined
|
|
else { return }
|
|
outlinedController.applyTheme(withScheme: containerScheme)
|
|
}
|
|
|
|
func addGestureRecognizer() {
|
|
let tapRecognizer = UITapGestureRecognizer(target: self,
|
|
action: #selector(tapDidTouch(sender: )))
|
|
self.scrollView.addGestureRecognizer(tapRecognizer)
|
|
}
|
|
|
|
// MARK: - Actions
|
|
|
|
@objc func tapDidTouch(sender: Any) {
|
|
self.view.endEditing(true)
|
|
}
|
|
|
|
}
|
|
|
|
extension TextFieldOutlinedSwiftExample: 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 == state {
|
|
if let range = fullString.rangeOfCharacter(from: CharacterSet.letters.inverted),
|
|
String(fullString[range]).characterCount > 0 {
|
|
stateController.setErrorText("Error: State can only contain letters",
|
|
errorAccessibilityValue: nil)
|
|
} else {
|
|
stateController.setErrorText(nil, errorAccessibilityValue: nil)
|
|
}
|
|
} else 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
|
|
}
|
|
}
|
|
|
|
extension TextFieldOutlinedSwiftExample: UITextViewDelegate {
|
|
func textViewDidEndEditing(_ textView: UITextView) {
|
|
print(textView.text)
|
|
}
|
|
}
|
|
|
|
// MARK: - Keyboard Handling
|
|
|
|
extension TextFieldOutlinedSwiftExample {
|
|
func registerKeyboardNotifications() {
|
|
let notificationCenter = NotificationCenter.default
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(keyboardWillShow(notif:)),
|
|
name: UIResponder.keyboardWillChangeFrameNotification,
|
|
object: nil)
|
|
notificationCenter.addObserver(
|
|
self,
|
|
selector: #selector(keyboardWillShow(notif:)),
|
|
name: UIResponder.keyboardWillShowNotification,
|
|
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 TextFieldOutlinedSwiftExample {
|
|
override var preferredStatusBarStyle: UIStatusBarStyle {
|
|
return .lightContent
|
|
}
|
|
}
|
|
|
|
extension TextFieldOutlinedSwiftExample {
|
|
|
|
@objc class func catalogMetadata() -> [String: Any] {
|
|
return [
|
|
"breadcrumbs": ["Text Field", "Outlined Fields & Text Areas"],
|
|
"primaryDemo": false,
|
|
"presentable": false,
|
|
]
|
|
}
|
|
}
|