0

我想在 iOS 联系人应用程序的创建/编辑联系人屏幕中创建 Eureka 行,使其外观和行为类似于邮政地址行。按下单元格中的相应按钮时,我需要显示标签或国家/地区选择器。基于尤里卡文档:

显示新视图控制器的每一行都必须符合 PresenterRowType 协议

然而这个协议是通用的。所以我的理解是,我每行不能显示多个子屏幕。我做对了吗?是否可以呈现多个子屏幕?

到目前为止,我所拥有的如下。

行协议:

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 }

}

邮政地址行基类:

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)
    }
}

邮政地址行最后:

final class PostalAddressRow: _PostalAddressRow<PostalAddressCell>, RowType {
    public required init(tag: String? = nil) {
        super.init(tag: tag)
        // TODO
        cellProvider = CellProvider<PostalAddressCell>(nibName: "PostalAddressCell")
    }
}

细胞:

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)
    }
}

楷模:

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
    }
}
4

0 回答 0