I want to implement storyboard localization in swift language. (means I want to localization for fix label and button text)
I have already idea about NSLocalizedString but I dont want to code for fix text Label
for e.g
NSLocalizedString("Welcome", comment: "")
I have already add Localizable.strings file as well as Main.string file for particular language. but I can not success in implement Localization
Follow below steps to localize Storyboard Elements:
Click on your project.
In Localizations section click on + icon and add language that you want to localize.
Once you add language you will see String file of that language.
Go to storyboard and click on UI Element that you want to localize.
Select identity inspector, In Document portion you will see Object ID that we need to use for localize that element.
Now goto localization file that created from step 3.
In that string file you will see Object ID of elements. Change value of that Object ID key it will reflect to that particular language only.
Once you have your localisation running, you can add extensions for UI elements, introducing easy localisation for them.
For UIlabel it will looks something like this:
public extension UILabel {
#IBInspectable public var localizedText: String? {
get {
return text
}
set {
text = NSLocalizedString(newValue ?? "", comment: "")
}
}
}
The #IBInspectable allows you setting the localisation key either from the storyboard and programatically too.
The storyboard localisation is the Apple provided way, but it bugs me a lot - not the most programmer-friendly for sure.
Bhumesh
I have used this library for in - app localisation. Which is very easy to use.
https://github.com/marmelroy/Localize-Swift
Now For Storyboard support I have Created Following extension that is IBDesignable So you can easily provide localised text from storyboard itself
1 ) Add This into new swift file
import Localize_Swift
#IBDesignable class LocalizableLabel: UILabel {
#IBInspectable var table :String? // Table
#IBInspectable var key:String? // KEY
#IBInspectable var extraTextToAppend:String? // Some text need to append , if any
override func awakeFromNib() {
guard let key = key else {return}
self.text = key.localized(using: table)
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
if let extraText = self.extraTextToAppend, let text = self.text {
self.text = text + extraText
}
}
#objc func setText () {
guard let key = key else {return}
self.text = key.localized(using: table)
if let extraText = self.extraTextToAppend, let text = self.text {
self.text = text + extraText
}
}
}
#IBDesignable class LocalizableButton: UIButton {
#IBInspectable var table :String?
#IBInspectable var key:String?
override func awakeFromNib() {
guard let key = key else {return}
self.setTitle(key.localized(using: table), for: .normal)
if let attributedText = self.attributedTitle(for: .normal) {
let mutableAttributedText = NSMutableAttributedString(attributedString: attributedText)
mutableAttributedText.replaceCharacters(in: NSMakeRange(0, mutableAttributedText.length), with: key.localized(using: table))
self.setAttributedTitle(mutableAttributedText, for: .normal)
}
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
}
#objc func setText () {
guard let key = key else {return}
self.setTitle(key.localized(using: table), for: .normal)
if let attributedText = self.attributedTitle(for: .normal) {
let mutableAttributedText = NSMutableAttributedString(attributedString: attributedText)
mutableAttributedText.replaceCharacters(in: NSMakeRange(0, mutableAttributedText.length), with: key.localized(using: table))
self.setAttributedTitle(mutableAttributedText, for: .normal)
}
}
}
#IBDesignable class LocalizeUINavigationItem: UINavigationItem {
#IBInspectable var table :String?
#IBInspectable var key:String?
override func awakeFromNib() {
guard let key = key else {return}
self.title = key.localized(using: table)
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
}
#objc func setText () {
guard let key = key else {return}
self.title = key.localized(using: table)
}
}
#IBDesignable class LocalizableUITextField: UITextField {
#IBInspectable var table_placeholder :String?
#IBInspectable var key_place_holder:String?
override func awakeFromNib() {
guard let key = key_place_holder else {return}
self.placeholder = key.localized(using: table_placeholder)
NotificationCenter.default.addObserver(self, selector: #selector(setText), name: NSNotification.Name(LCLLanguageChangeNotification), object: nil)
}
#objc func setText () {
guard let key = key_place_holder else {return}
self.placeholder = key.localized(using: table_placeholder)
}
}
2) Goto Storyboard set class to label and provide the key
3) Run and test
class ViewController: UIViewController {
#IBOutlet weak var resetOutlet: MyButton! {
didSet {
resetOutlet.setTitle("RESET".localized().uppercased(), for: .normal)
}
}
}
extension String {
func localized(tableName: String = "Localizable") -> String {
if let languageCode = Locale.current.languageCode, let preferredLanguagesFirst = Locale.preferredLanguages.first?.prefix(2) {
if languageCode != preferredLanguagesFirst {
if let path = Bundle.main.path(forResource: "en", ofType: "lproj") {
let bundle = Bundle.init(path: path)
return NSLocalizedString(self, tableName: tableName, bundle: bundle!, value: self, comment: "")
}
}
}
return NSLocalizedString(self, tableName: tableName, value: self, comment: "")
}
}
Related
I am using two languages 1) english 2) French
In my project I need to change label language text without creating IBOutlet
I have created two string files called Localizable(English) and Localizable(French)
added text constants like this in project
code: I have created changeLanguage method and saved its language code in UserDefaults
static func changeLanguage(with : String) {
if with == "fr"{
Bundle.setLanguage("fr")
CommonUserDefaults.accessInstance.save("fr", forType: .languageCode)
}
else {
Bundle.setLanguage("en")
CommonUserDefaults.accessInstance.save("en", forType: .languageCode)
}
}
extension String {
var localizedWithLanguage : String{
NSLocalizedString(self, comment: "")
}
}
private var bundleKey: UInt8 = 0
class AnyLanguageBundle: Bundle {
override func localizedString(forKey key: String,
value: String?,
table tableName: String?) -> String {
guard let path = objc_getAssociatedObject(self, &bundleKey) as? String,
let bundle = Bundle(path: path) else {
return super.localizedString(forKey: key, value: value, table: tableName)
}
return bundle.localizedString(forKey: key, value: value, table: tableName)
}
}
extension Bundle {
class func setLanguage(_ language: String) {
defer {
object_setClass(Bundle.main, AnyLanguageBundle.self)
}
objc_setAssociatedObject(Bundle.main, &bundleKey, Bundle.main.path(forResource: language, ofType: "lproj"), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
and changing language like this in viewcontroller
class LoginVC: UIViewController {
#IBOutlet weak var emailTF: UITextField!
#IBOutlet weak var passwordTF: UITextField!
override func viewWillAppear(_ animated: Bool) {
Bundle.setLanguage("fr")
emailTF.placeholder = emailTF.placeholder?.localizedWithLanguage
passwordTF.placeholder = passwordTF.placeholder?.localizedWithLanguage
}
}
If I write emailTF.placeholder?.localizedWithLanguage like this then email and password placeholder text changing but i need to change static label text also but that is not changing why?
below o/p "Welcome back" and "Enter Your Email and Password" why these texts are not changed
how to change these label texts without crating IBOutlet, please guide me
I transfer data from the sharing extension to my main application with UserDefaults and open the application (goToApp()) after hitting the "post" button. However, the view of my app is not redrawn and the text remains the same "Share Extension Example". Here's how I'm trying to do it:
class ShareViewController: SLComposeServiceViewController {
private var textString: String?
override func isContentValid() -> Bool {
if let currentMessage = contentText {
self.textString = currentMessage
}
return true
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didSelectPost() {
UserDefaults.standard.set(self.textString!, forKey: "text")
gotoApp()
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
func gotoApp() {
guard let url = URL(string: "example://") else { return }
let selectorOpenURL = sel_registerName("openURL:")
var responder: UIResponder? = self
while responder != nil {
if responder?.responds(to: selectorOpenURL) == true {
responder?.perform(selectorOpenURL, with: url)
}
responder = responder?.next
}
}
}
And the project to which I am trying to transfer data:
class ViewController: UIViewController {
private let mainVStack = UIStackView()
private let backgroundView = UIImageView()
private let titleLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
configureMainStack()
configureTitleLabel()
}
}
// MARK: - UI Elements
private extension ViewController {
func configureMainStack() {
mainVStack.distribution = .fillProportionally
mainVStack.embed(asSubviewTo: view, inset: 40)
}
func configureTitleLabel() {
titleLabel.textAlignment = .center
titleLabel.textColor = .blue
if let text = UserDefaults.object(forKey: "text") as? String {
titleLabel.text = text
} else {
titleLabel.text = "Share Extension Example"
}
let titleContainerView = UIView()
titleLabel.embedIn(titleContainerView, hInset: 0, vInset: 100)
mainVStack.addArrangedSubview(titleContainerView)
}
}
I'm developing an iOS app with five languages.I have done all the coding and now I'm trying to switch between languages using the button.But can't change the language..I found the problem but cant find the solution.
[NSBundle setLanguage:code];
Problem this line not calling..
Any help please? thanks..
Use localization to change the language of the application without restart.
Go to project -> select your project in the document outline -> Add the new language.
Add Localized.strings files to your project.
Add the strings which are used for localization in Localizable.strings(XXX)
For English "hello" = "Hello World";
For Hindi "hello" = "नमस्ते दुनिया";
Code:
import UIKit
extension String {
var localized: String {
let lang = currentLanguage()
let path = Bundle.main.path(forResource: lang, ofType: "lproj")
let bundle = Bundle(path: path!)
return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}
//Remove here and add these in utilities class
func saveLanguage(_ lang: String) {
UserDefaults.standard.set(lang, forKey: "Locale")
UserDefaults.standard.synchronize()
}
func currentLanguage() -> String {
return UserDefaults.standard.string(forKey: "Locale") ?? ""
}
}
enum Language: String {
case english = "English"
case hindi = "हिंदी"
}
class ViewController: UIViewController {
var language = Language.english
override func viewDidLoad() {
super.viewDidLoad()
//Initial Setup
String().saveLanguage("en")
languageLabel.text = "hello".localized
languageButton.setTitle(language.rawValue, for: .normal)
}
func updateLanguage() {
if language == .english {
String().saveLanguage("hi")
language = .hindi
} else {
String().saveLanguage("en")
language = .english
}
languageLabel.text = "hello".localized
languageButton.setTitle(language.rawValue, for: .normal)
}
#IBOutlet weak var languageLabel: UILabel!
#IBOutlet weak var languageButton: UIButton!
#IBAction func changeLanguageButtonTapped(_ sender: UIButton) {
updateLanguage()
}
}
I have a custom UITableViewCell which has a data model array, and a UILabel as this:
class ItemCustomizationCollectionViewCell: UITableViewCell {
var customizationData: CustomizationData?
let priceLabel: UILabel = {
let label = UILabel()
label.font = UIFont.boldSystemFont(ofSize: 18)
label.textAlignment = .left
label.textColor = UIColor.gray
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
//other init and constraints
}
Now, the CustomizationData looks like this:
class CustomizationData {
let title: String
var customizationOptions: [PickerOption]
var choosenOption: PickerOption?
init(title: String, customizationOptions: [PickerOption], choosenOption: PickerOption?) {
self.title = title
self.customizationOptions = customizationOptions
self.choosenOption = choosenOption
}
}
and the PickerOption is:
class PickerOption {
let title: String
let price: String
init(title: String, price: String) {
self.title = title
self.price = price
}
}
My Question is:
I want to listen to customizationData's choosenOption gets set, and get the title to change the UILabel's text to the choosenOption's price.
I tried to add a didSet to the customizationData but it's not being called as one of its properties is changing. Not the customizationData itself.
What you can do is possible, but to trigger a didSet customizationOptions of you need to change its value, basically in 2 ways:
if customizationOptions is a reference type, you need to change it with another reference
if customizationOptions is a value type such as an Array, the didSet should be called also if you change a value inside it, because the array change the struct completely.
To understand better here is an example.
Using a value type:
class Mine {
var list: [String] = ["Hello"] {
didSet {
print("Array changed \(list)")
}
}
}
var mine = Mine()
mine.list.append("World") //Array changed ["Hello", "World"]
Using a reference type:
class Mine {
var list: NSArray = ["Hello"] {
didSet {
print("Array changed \(list)")
}
}
}
var mine = Mine()
mine.list.adding("World")
//Doesn't print nothing
mine.list = ["World"]
//print World
I think you can do this using delegation. Create a CustomizationDataDelegate protocol:
protocol CustomizationDataDelegate {
func choosenOptionDidChange(to newValue: PickerOption)
}
Then create a delegate property in the CustomizationData class, for instance:
internal var delegate : CustomizationDataDelegate?
And add a didSet to choosenOption:
var choosenOption : PickerOption? {
didSet{
if let _ = choosenOption {
delegate?.choosenOptionDidChange(to choosenOption!)
}
}
}
Then register the ItemCustomizationCollectionViewCell to the CustomizationDataDelegate protocol, and then when you have some customization data, make sure the delegate is set to the cell's value:
class ItemCustomizationCollectionViewCell: UITableViewCell, CustomizationDataDelegate {
var customizationData: CustomizationData? {
didSet {
if let _ = customizationData {
if let _ = customizationData!.choosenOption {
// You need this in case the choosenOption has already been set
choosenOptionDidChange(to: customizationData!.choosenOption)
}
// You need this to listen for future changes
customizationData!.delegate = self
}
}
}
let priceLabel: UILabel = {
// ...
// In your class' function declarations...
//# MARK:- CustomizationDataDelegate methods
func choosenOptionDidChange(to newValue: PickerOption) -> Void {
// Update the label's value here based on newValue
}
}
Hope that helps.
I'd like to create Eureka row to look and behave as Postal Address Row in create/edit Contact Screen in iOS Contacts App. I need to present Label or Country Picker when corresponding button in cell is pressed. Based on Eureka documentation:
every row that displays a new view controller must conform to PresenterRowType protocol
However this protocol is generic. So my understanding is that I can't show more than one child screen per row. Do I get this right? Is it possible to present more than one child screen?
What I have so far follows.
Row Protocols:
protocol PostalAddressFormatterConformance: class {
var streetUseFormatterDuringInput: Bool { get set }
var streetFormatter: Formatter? { get set }
var stateUseFormatterDuringInput: Bool { get set }
var stateFormatter: Formatter? { get set }
var postalCodeUseFormatterDuringInput: Bool { get set }
var postalCodeFormatter: Formatter? { get set }
var cityUseFormatterDuringInput: Bool { get set }
var cityFormatter: Formatter? { get set }
}
protocol LabeledRowConformance {
func onLabelButtonDidPress()
}
protocol CountryRowConformance {
func onCountryButtonDidPress()
}
protocol PostalAddressRowConformance: PostalAddressFormatterConformance, LabeledRowConformance, CountryRowConformance {
var placeholderColor : UIColor? { get set }
var streetPlaceholder : String? { get set }
var statePlaceholder : String? { get set }
var postalCodePlaceholder : String? { get set }
var cityPlaceholder : String? { get set }
}
Postal Address Row Base class:
class _PostalAddressRow<Cell: CellType>: Row<Cell>, PostalAddressRowConformance, CountryRowConformance, LabeledRowConformance, KeyboardReturnHandler where Cell: BaseCell, Cell: PostalAddressCellConformance {
//MARK: - LabeledRowConformance
func onLabelButtonDidPress() {
// TODO: Present Label Picker Screen
}
//MARK: - CountryRowConformance
func onCountryButtonDidPress() {
// TODO: Present Country Picker Screen
}
//MARK: - KeyboardReturnHandler
/// Configuration for the keyboardReturnType of this row
var keyboardReturnType : KeyboardReturnTypeConfiguration?
//MARK: - PostalAddressRowConformance
/// The textColor for the textField's placeholder
var placeholderColor : UIColor?
/// The placeholder for the street textField
var streetPlaceholder : String?
/// The placeholder for the state textField
var statePlaceholder : String?
/// The placeholder for the zip textField
var postalCodePlaceholder : String?
/// The placeholder for the city textField
var cityPlaceholder : String?
/// A formatter to be used to format the user's input for street
var streetFormatter: Formatter?
/// A formatter to be used to format the user's input for state
var stateFormatter: Formatter?
/// A formatter to be used to format the user's input for zip
var postalCodeFormatter: Formatter?
/// A formatter to be used to format the user's input for city
var cityFormatter: Formatter?
/// If the formatter should be used while the user is editing the street.
var streetUseFormatterDuringInput: Bool
/// If the formatter should be used while the user is editing the state.
var stateUseFormatterDuringInput: Bool
/// If the formatter should be used while the user is editing the zip.
var postalCodeUseFormatterDuringInput: Bool
/// If the formatter should be used while the user is editing the city.
var cityUseFormatterDuringInput: Bool
public required init(tag: String?) {
streetUseFormatterDuringInput = false
stateUseFormatterDuringInput = false
postalCodeUseFormatterDuringInput = false
cityUseFormatterDuringInput = false
super.init(tag: tag)
}
}
Postal Address Row Final:
final class PostalAddressRow: _PostalAddressRow<PostalAddressCell>, RowType {
public required init(tag: String? = nil) {
super.init(tag: tag)
// TODO
cellProvider = CellProvider<PostalAddressCell>(nibName: "PostalAddressCell")
}
}
Cell:
public protocol CountryCellConformance {
var countryButton: UIButton? { get }
}
public protocol PostalAddressCellConformance: CountryCellConformance {
var streetTextField: UITextField? { get }
var stateTextField: UITextField? { get }
var postalCodeTextField: UITextField? { get }
var cityTextField: UITextField? { get }
}
class _PostalAddressCell<T: PostalAddressType>: Cell<T>, PostalAddressCellConformance, UITextFieldDelegate, CellType {
#IBOutlet weak var changeLabelButton: UIButton!
//MARK: - CountryCellConformance
#IBOutlet weak var countryButton: UIButton?
//MARK: - PostalAddressCellConformance
#IBOutlet weak var streetTextField: UITextField?
#IBOutlet weak var stateTextField: UITextField?
#IBOutlet weak var postalCodeTextField: UITextField?
#IBOutlet weak var cityTextField: UITextField?
// ??? Style Color
#IBOutlet var separatorViews: [UIView]!
// Helper
var textFieldOrdering: [UITextField?] = []
//MARK: - Lifecycle
public required init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
open override func awakeFromNib() {
super.awakeFromNib()
textFieldOrdering = [streetTextField, stateTextField, postalCodeTextField, cityTextField]
}
deinit {
streetTextField?.delegate = nil
streetTextField?.removeTarget(self, action: nil, for: .allEvents)
stateTextField?.delegate = nil
stateTextField?.removeTarget(self, action: nil, for: .allEvents)
postalCodeTextField?.delegate = nil
postalCodeTextField?.removeTarget(self, action: nil, for: .allEvents)
cityTextField?.delegate = nil
cityTextField?.removeTarget(self, action: nil, for: .allEvents)
}
//MARK: - Actions
#IBAction func changeLabelButtonPressed(_ sender: Any) {
if let rowConformance = row as? LabeledRowConformance {
rowConformance.onLabelButtonDidPress()
}
}
#IBAction func countryButtonPressed(_ sender: Any) {
if let rowConformance = row as? CountryRowConformance {
rowConformance.onCountryButtonDidPress()
}
}
func internalNavigationAction(_ sender: UIBarButtonItem) {
guard let inputAccesoryView = inputAccessoryView as? NavigationAccessoryView else { return }
var index = 0
for field in textFieldOrdering {
if field?.isFirstResponder == true {
let _ = sender == inputAccesoryView.previousButton ? textFieldOrdering[index-1]?.becomeFirstResponder() : textFieldOrdering[index+1]?.becomeFirstResponder()
break
}
index += 1
}
}
func textFieldDidChange(_ textField : UITextField){
if row.baseValue == nil{
row.baseValue = PostalAddress()
}
guard let textValue = textField.text else {
switch(textField) {
case let field where field == streetTextField:
row.value?.street = nil
case let field where field == stateTextField:
row.value?.state = nil
case let field where field == postalCodeTextField:
row.value?.postalCode = nil
case let field where field == cityTextField:
row.value?.city = nil
default:
break
}
return
}
if let rowConformance = row as? PostalAddressRowConformance {
var useFormatterDuringInput = false
var valueFormatter: Formatter?
switch(textField) {
case let field where field == streetTextField:
useFormatterDuringInput = rowConformance.streetUseFormatterDuringInput
valueFormatter = rowConformance.streetFormatter
case let field where field == stateTextField:
useFormatterDuringInput = rowConformance.stateUseFormatterDuringInput
valueFormatter = rowConformance.stateFormatter
case let field where field == postalCodeTextField:
useFormatterDuringInput = rowConformance.postalCodeUseFormatterDuringInput
valueFormatter = rowConformance.postalCodeFormatter
case let field where field == cityTextField:
useFormatterDuringInput = rowConformance.cityUseFormatterDuringInput
valueFormatter = rowConformance.cityFormatter
default:
break
}
if let formatter = valueFormatter, useFormatterDuringInput{
let value: AutoreleasingUnsafeMutablePointer<AnyObject?> = AutoreleasingUnsafeMutablePointer<AnyObject?>.init(UnsafeMutablePointer<T>.allocate(capacity: 1))
let errorDesc: AutoreleasingUnsafeMutablePointer<NSString?>? = nil
if formatter.getObjectValue(value, for: textValue, errorDescription: errorDesc) {
switch(textField){
case let field where field == streetTextField:
row.value?.street = value.pointee as? String
case let field where field == stateTextField:
row.value?.state = value.pointee as? String
case let field where field == postalCodeTextField:
row.value?.postalCode = value.pointee as? String
case let field where field == cityTextField:
row.value?.city = value.pointee as? String
default:
break
}
if var selStartPos = textField.selectedTextRange?.start {
let oldVal = textField.text
textField.text = row.displayValueFor?(row.value)
if let f = formatter as? FormatterProtocol {
selStartPos = f.getNewPosition(forPosition: selStartPos, inTextInput: textField, oldValue: oldVal, newValue: textField.text)
}
textField.selectedTextRange = textField.textRange(from: selStartPos, to: selStartPos)
}
return
}
}
}
guard !textValue.isEmpty else {
switch(textField){
case let field where field == streetTextField:
row.value?.street = nil
case let field where field == stateTextField:
row.value?.state = nil
case let field where field == postalCodeTextField:
row.value?.postalCode = nil
case let field where field == cityTextField:
row.value?.city = nil
default:
break
}
return
}
switch(textField){
case let field where field == streetTextField:
row.value?.street = textValue
case let field where field == stateTextField:
row.value?.state = textValue
case let field where field == postalCodeTextField:
row.value?.postalCode = textValue
case let field where field == cityTextField:
row.value?.city = textValue
default:
break
}
}
//MARK: - Setup
override func setup() {
super.setup()
height = { 149 }
selectionStyle = .none
for textField in textFieldOrdering {
textField?.addTarget(self,
action: #selector(_PostalAddressCell.textFieldDidChange(_:)), // TODO: Move in extension
for: .editingChanged)
textField?.textAlignment = .left
textField?.clearButtonMode = .whileEditing
textField?.delegate = self
textField?.font = .preferredFont(forTextStyle: .body)
}
for separator in separatorViews {
separator.backgroundColor = .gray
}
}
//MARK: - Update
override func update() {
super.update()
textLabel?.text = nil
detailTextLabel?.text = nil
imageView?.image = nil
for textField in textFieldOrdering {
textField?.isEnabled = !row.isDisabled
textField?.textColor = row.isDisabled ? .gray : .black
textField?.autocorrectionType = .no
textField?.autocapitalizationType = .words
}
streetTextField?.text = row.value?.street
streetTextField?.keyboardType = .asciiCapable
stateTextField?.text = row.value?.state
stateTextField?.keyboardType = .asciiCapable
postalCodeTextField?.text = row.value?.postalCode
postalCodeTextField?.keyboardType = .numbersAndPunctuation
cityTextField?.text = row.value?.city
cityTextField?.keyboardType = .asciiCapable
if let rowConformance = row as? PostalAddressRowConformance {
setPlaceholderToTextField(textField: streetTextField, placeholder: rowConformance.streetPlaceholder)
setPlaceholderToTextField(textField: stateTextField, placeholder: rowConformance.statePlaceholder)
setPlaceholderToTextField(textField: postalCodeTextField, placeholder: rowConformance.postalCodePlaceholder)
setPlaceholderToTextField(textField: cityTextField, placeholder: rowConformance.cityPlaceholder)
}
countryButton?.setTitle(String(describing: row.value?.country), for: .normal)
}
//MARK: - BaseCell Responder
override func cellCanBecomeFirstResponder() -> Bool {
return !row.isDisabled && (
streetTextField?.canBecomeFirstResponder == true ||
stateTextField?.canBecomeFirstResponder == true ||
postalCodeTextField?.canBecomeFirstResponder == true ||
cityTextField?.canBecomeFirstResponder == true
)
}
override func cellBecomeFirstResponder(withDirection direction: Direction) -> Bool {
return direction == .down ? textFieldOrdering.first??.becomeFirstResponder() ?? false : textFieldOrdering.last??.becomeFirstResponder() ?? false
}
override func cellResignFirstResponder() -> Bool {
return streetTextField?.resignFirstResponder() ?? true
&& stateTextField?.resignFirstResponder() ?? true
&& postalCodeTextField?.resignFirstResponder() ?? true
&& stateTextField?.resignFirstResponder() ?? true
&& cityTextField?.resignFirstResponder() ?? true
}
override var inputAccessoryView: UIView? {
if let v = formViewController()?.inputAccessoryView(for: row) as? NavigationAccessoryView {
guard let first = textFieldOrdering.first, let last = textFieldOrdering.last, first != last else { return v }
if first?.isFirstResponder == true {
v.nextButton.isEnabled = true
v.nextButton.target = self
v.nextButton.action = #selector(_PostalAddressCell.internalNavigationAction(_:)) // TODO: Move in extension
} else if last?.isFirstResponder == true {
v.previousButton.target = self
v.previousButton.action = #selector(_PostalAddressCell.internalNavigationAction(_:))
v.previousButton.isEnabled = true
} else {
v.previousButton.target = self
v.previousButton.action = #selector(_PostalAddressCell.internalNavigationAction(_:))
v.nextButton.target = self
v.nextButton.action = #selector(_PostalAddressCell.internalNavigationAction(_:))
v.previousButton.isEnabled = true
v.nextButton.isEnabled = true
}
return v
}
return super.inputAccessoryView
}
//MARK: - UITextFieldDelegate
func textFieldDidBeginEditing(_ textField: UITextField) {
formViewController()?.beginEditing(of: self)
formViewController()?.textInputDidBeginEditing(textField, cell: self)
}
func textFieldDidEndEditing(_ textField: UITextField) {
formViewController()?.endEditing(of: self)
formViewController()?.textInputDidEndEditing(textField, cell: self)
textFieldDidChange(textField)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
return formViewController()?.textInputShouldReturn(textField, cell: self) ?? true
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return formViewController()?.textInputShouldEndEditing(textField, cell: self) ?? true
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
return formViewController()?.textInputShouldBeginEditing(textField, cell: self) ?? true
}
func textFieldShouldClear(_ textField: UITextField) -> Bool {
return formViewController()?.textInputShouldClear(textField, cell: self) ?? true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
return formViewController()?.textInputShouldEndEditing(textField, cell: self) ?? true
}
//MARK: - Private
private func setPlaceholderToTextField(textField: UITextField?, placeholder: String?) {
if let placeholder = placeholder, let textField = textField {
if let color = (row as? PostalAddressRowConformance)?.placeholderColor {
textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSForegroundColorAttributeName: color])
} else {
textField.placeholder = placeholder
}
}
}
}
final class PostalAddressCell: _PostalAddressCell<PostalAddress> {
public required init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Models:
protocol CountryType: Equatable {
var country: Country? { get set }
}
func == <T: CountryType>(lhs: T, rhs: T) -> Bool {
return lhs.country == rhs.country
}
//
protocol PostalAddressType: CountryType {
var street: String? { get set }
var state: String? { get set }
var postalCode: String? { get set }
var city: String? { get set }
}
func == <T: PostalAddressType>(lhs: T, rhs: T) -> Bool {
return lhs.street == rhs.street && lhs.state == rhs.state && lhs.postalCode == rhs.postalCode && lhs.city == rhs.city && lhs.country == rhs.country
}
//
class PostalAddress: PostalAddressType {
var street: String?
var state: String?
var postalCode: String?
var city: String?
var country: Country?
public init() {}
public init(street: String?, state: String?, postalCode: String?, city: String?, country: Country?) {
self.street = street
self.state = state
self.postalCode = postalCode
self.city = city
self.country = country
}
}