Bilal Durnagöl
Bilal Durnagöl

Reputation: 37

Error occurs when I use IBOutlet for connection to custom component in iOS 12.5.7

Error occurs when I use IBOutlet for connection to DefaultTextField in iOS 12.5.7. But if I use programmatic application, no error occurs.

This error usually occurs iPhone 6 and iPhone 6 Plus in iOS 12.5.7. Apple gives a meaningless error. When I use programmatic methods, no error occur. I think this error may be due to @IBDesignable.

Code with error like this.

@IBOutlet weak var usernameTextField: DefaultTextField!
@IBOutlet weak var passwordTextField: DefaultTextField!

Code without error like this.

  private let usernameTextField: DefaultTextField = {
        let textField = DefaultTextField()
        textField.placeholder = "Üye No / TC Kimlik No"
        textField.stringFormat = "XXXXXXXXXXX"
        textField.keyboardType = .numberPad
        textField.compareOption = .regularExpression
        textField.autocapitalizationType = .none
        textField.textContentType = .username

        return textField
    }()

    private let passwordTextField: DefaultTextField = {
        let textField = DefaultTextField()
        textField.placeholder = "Şifre"
        textField.rightIcon = R.Image.TJK.passwordTextFieldEyeIcon
        textField.isSecureTextEntry = true
        textField.autocapitalizationType = .none
        textField.returnType = .done

        return textField
    }()

Error like this.

          Crashed: com.apple.main-thread
0  libobjc.A.dylib                0x1d528 objc_msgSend + 8
1  UIKitCore                      0xd3e5e0 -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 172
2  UIKitCore                      0xd3e7c4 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 116
3  UIKitCore                      0xd3e6d8 -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 420
4  UIKitCore                      0xd3e7c4 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 116
5  UIKitCore                      0xd3e6d8 -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 420
6  UIKitCore                      0xd3e7c4 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 116
7  UIKitCore                      0xd3e6d8 -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 420
8  UIKitCore                      0xd3e7c4 __85-[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]_block_invoke + 116
9  UIKitCore                      0xd3e6d8 -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 420
10 UIKitCore                      0xd4dbf4 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 456
11 UIKitCore                      0x3501a0 -[_UIParallaxDimmingView didMoveToWindow] + 176
12 UIKitCore                      0xd4b760 -[UIView(Internal) _didMoveFromWindow:toWindow:] + 1604
13 UIKitCore                      0xd4b3b4 -[UIView(Internal) _didMoveFromWindow:toWindow:] + 664
14 UIKitCore                      0xd3edc4 __45-[UIView(Hierarchy) _postMovedFromSuperview:]_block_invoke + 156
15 UIKitCore                      0xd3ec8c -[UIView(Hierarchy) _postMovedFromSuperview:] + 760
16 UIKitCore                      0xd4e0f8 -[UIView(Internal) _addSubview:positioned:relativeTo:] + 1740
17 UIKitCore                      0x34b884 __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke_2 + 1716
18 UIKitCore                      0xd468f0 +[UIView(Animation) performWithoutAnimation:] + 104
19 UIKitCore                      0x34b17c __53-[_UINavigationParallaxTransition animateTransition:]_block_invoke + 228
20 UIKitCore                      0xd4c87c +[UIView(Internal) _performBlockDelayingTriggeringResponderEvents:] + 220
21 UIKitCore                      0x34ab98 -[_UINavigationParallaxTransition animateTransition:] + 1096
22 UIKitCore                      0x271090 -[UINavigationController _startCustomTransition:] + 3484
23 UIKitCore                      0x284714 -[UINavigationController _startDeferredTransitionIfNeeded:] + 708
24 UIKitCore                      0x285b3c -[UINavigationController __viewWillLayoutSubviews] + 164
25 UIKitCore                      0x268d4c -[UILayoutContainerView layoutSubviews] + 224
26 UIKitCore                      0xd54170 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1292
27 QuartzCore                     0x13ec60 -[CALayer layoutSublayers] + 184
28 QuartzCore                     0x143c08 CA::Layer::layout_if_needed(CA::Transaction*) + 332
29 QuartzCore                     0xa63e4 CA::Context::commit_transaction(CA::Transaction*) + 348
30 QuartzCore                     0xd4620 CA::Transaction::commit() + 640
31 QuartzCore                     0xd515c CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 92
32 CoreFoundation                 0xaa4fc __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
33 CoreFoundation                 0xa5224 __CFRunLoopDoObservers + 412
34 CoreFoundation                 0xa57a0 __CFRunLoopRun + 1228
35 CoreFoundation                 0xa4fb4 CFRunLoopRunSpecific + 436
36 GraphicsServices               0xa79c GSEventRunModal + 104
37 UIKitCore                      0x8bcc38 UIApplicationMain + 212
38 Hipodrom                       0x80bc main + 15 (main.swift:15)
39 libdyld.dylib                  0x18e0 start + 4
    

This is my custom component code.

import UIKit

@MainActor
@objc protocol DefaultTextFieldDelegate: AnyObject {
    @objc optional func didTapRightButton(_ textField: DefaultTextField)
    @objc optional func textFieldDidEndEditing(_ textField: DefaultTextField)
    @objc optional func textField(_ textField: DefaultTextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
    @objc optional func textFieldShouldReturn(_ textField: DefaultTextField) -> Bool
    @objc optional func textFieldDidChange(_ textField: DefaultTextField)
    @objc optional func textFieldDidBeginEditing(_ textField: DefaultTextField)
    @objc optional func didTapDoneButton(_ textField: DefaultTextField)
}

@IBDesignable
@objc(DefaultTextField)
class DefaultTextField: UIView {
    
    enum Status {
        case `default`
        case success
        case failure(message: String?)
        case focus
    }
    
    // MARK: - PROPERTIES(s)
    
    var keyboardType: UIKeyboardType = .default {
        didSet {
            textField.keyboardType = keyboardType
        }
    }
    
    var autocapitalizationType: UITextAutocapitalizationType = .words {
        didSet {
            textField.autocapitalizationType = autocapitalizationType
        }
    }
    
    var status: Status = .default {
        didSet {
            handleRigthViewStatus(with: status)
        }
    }
    
    var compareOption: NSString.CompareOptions?
    
    var textContentType: UITextContentType? {
        didSet {
            textField.textContentType = textContentType
        }
    }
    
    var returnType: UIReturnKeyType = .default {
        didSet {
            textField.returnKeyType = returnType
        }
    }
    
    weak var delegate: DefaultTextFieldDelegate?
    
    // MARK: - IBINSPECTABLE(s)
    
    /// use with X
    @IBInspectable
    var stringFormat: String?
    
    @IBInspectable
    var autoLocalization: Bool = true
    
    @IBInspectable
    var placeholder: String? {
        didSet {
            placeholderLabel.text = placeholder
        }
    }
    
    @IBInspectable
    var isSecureTextEntry: Bool = false {
        didSet {
            textField.isSecureTextEntry = isSecureTextEntry
        }
    }
    
    @IBInspectable
    var rightIcon: UIImage? {
        didSet {
            rightButton.setImage(rightIcon, for: .normal)
        }
    }
    
    @IBInspectable
    var text: String? {
        get {
            textField.text
        } set {
            DispatchQueue.main.async {[weak self] in
                self?.textField.text = newValue
                self?.updateFloatingLabel()
            }
        }
    }
    
    // MARK: - COMPONENT(s)
    
    var stackView: UIStackView = {
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.distribution = .fill
        stackView.spacing = 2
        return stackView
    }()
    
    var backgroundView: UIView = {
        let view = UIView()
        view.layer.cornerRadius = 2.0
        view.borderWidth = 1.0
        view.layer.masksToBounds = true
        return view
    }()
    
    var placeholderLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 1
        label.textColor = R.Color.gray100
        label.textAlignment = .left
        label.font = R.Fonts.SFCompactDisplay.medium(size: 16)
        label.isUserInteractionEnabled = true
        label.sizeToFit()
        return label
    }()
    
    var errorLabel: UILabel = {
        let label = UILabel()
        label.numberOfLines = 1
        label.textColor = .red
        label.textAlignment = .left
        label.font = R.Fonts.SFCompactDisplay.regular(size: 12)
        label.sizeToFit()
        return label
    }()
    
    var rightButton: UIButton = {
        let button = UIButton()
        button.imageView?.contentMode = .scaleAspectFit
        if #available(iOS 15.0, *) {
            button.configuration = nil
        }
        button.tintColor = R.Color.TJK.loginDisable
        return button
    }()
    
    var textField: UITextField = {
        let textField = UITextField()
        textField.borderStyle = .none
        textField.tintColor = R.Color.textFieldTextColor
        return textField
    }()
    
    // MARK: - INIT(s)
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupUI()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupUI()
    }
    
    // MARK: - OVERRIDE(s)
    
    public override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        setupUI()
    }
    
    @discardableResult
    internal override func becomeFirstResponder() -> Bool {
        let becameFirstResponder = super.becomeFirstResponder()
        textField.becomeFirstResponder()
        updateFloatingLabel()
        return becameFirstResponder
    }
    
    @discardableResult
    internal override func resignFirstResponder() -> Bool {
        let resignedFirstResponder = super.resignFirstResponder()
        textField.resignFirstResponder()
        updateFloatingLabel()
        return resignedFirstResponder
    }
    
    // MARK: - PUBLIC METHOD(s)
    
    func stringFormatter(
        _ text: String,
        range: NSRange,
        replacementString string: String) -> String {
            let input = (text as NSString)
            guard let stringFormat = stringFormat else { return "" }
            let newString = input.replacingCharacters(in: range, with: string)
            return newString.stringFormatted(with: stringFormat)
        }
    
    // MARK: - PRIVATE METHOD(s)
    
    private func setupUI() {
        // setup view
        
        stackViewAddSubview()
        backgroundViewAddSubview()
        errorLabelAddSubview()
        
        status = .default
        
        // setup placeholder
        placeholderAddSubview()
        updatePlaceholder()
        
        // setup textfield
        textFieldAddSubview()
        updateTextField()
        textField.delegate = self
        textField.tag = self.tag
        textField.returnKeyType = returnType
        // setup right button
        rightButtonAddSubview()
        
        updateFloatingLabel()
        addToolbarToTextField()
        if autoLocalization == true, let key = placeholder {
            placeholderLabel.text = NSLocalizedString(key, comment: "")
        }
        
        // Targets
        textField.addTarget(self, action: #selector(self.valueChanged), for: .editingDidBegin)
        textField.addTarget(self, action: #selector(self.valueChanged), for: .editingDidEnd)
        textField.addTarget(self, action: #selector(self.valueChanged), for: .editingChanged)
        let placeholderTapped = UITapGestureRecognizer(target: self, action: #selector(self.placeholderTapped))
        placeholderLabel.addGestureRecognizer(placeholderTapped)
    }
    
    private func stackViewAddSubview() {
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
            stackView.topAnchor.constraint(equalTo: topAnchor),
            stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
            ])
    }
    
    private func backgroundViewAddSubview() {
        stackView.addArrangedSubview(backgroundView)
        backgroundView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            backgroundView.heightAnchor.constraint(equalToConstant: 45.0)
        ])
    }
    
    private func placeholderAddSubview() {
        backgroundView.addSubview(placeholderLabel)
        placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            placeholderLabel.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 20.0),
            placeholderLabel.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: -32.0),
            placeholderLabel.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor)
        ])
    }
    
    private func errorLabelAddSubview() {
        stackView.addArrangedSubview(errorLabel)
        errorLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            errorLabel.heightAnchor.constraint(equalToConstant: 18.0)
        ])
    }
    
    private func textFieldAddSubview() {
        backgroundView.addSubview(textField)
        textField.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            textField.leadingAnchor.constraint(equalTo: backgroundView.leadingAnchor, constant: 20.0),
            textField.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: -32.0),
            textField.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor)
        ])
    }
    
    private func rightButtonAddSubview() {
        backgroundView.addSubview(rightButton)
        rightButton.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            rightButton.trailingAnchor.constraint(equalTo: backgroundView.trailingAnchor, constant: -12.0),
            rightButton.centerYAnchor.constraint(equalTo: backgroundView.centerYAnchor),
            rightButton.widthAnchor.constraint(equalToConstant: 20)
        ])
        
        rightButton.addTarget(self, action: #selector(tappedRightButton), for: .touchUpInside)
    }
    
    /// If the text is empty, it brings the placeholder to the middle. If it is full, it brings it up.
    private func updateFloatingLabel() {
        UIView.animate(withDuration: 0.2) {[weak self] in
            guard let self = self else { return }
            placeholderLabel.font = R.Fonts.SFCompactDisplay.semibold(size: 12)
            if textField.text?.isEmpty == false {
                self.placeholderLabel.transform = CGAffineTransform(
                    translationX: 0,
                    y: -(backgroundView.frame.height/2 - placeholderLabel.frame.height/2) + 4
                )
                self.textField.transform = CGAffineTransform(
                    translationX: 0,
                    y: (backgroundView.frame.height/2 - textField.frame.height/2) - 4
                )
            } else {
                self.placeholderLabel.transform = .identity
                self.textField.transform = .identity
                self.placeholderLabel.font = R.Fonts.SFCompactDisplay.medium(size: 16)
            }
            
            setNeedsDisplay()
        }
    }
    
    /// deletes the actual placeholder and writes it to placeholderLabel
    private func updatePlaceholder() {
        if placeholder?.isEmpty == false {
            placeholderLabel.text = placeholder
            placeholder = nil
        }
    }
    
    /// deletes the actual text and writes it to text
    private func updateTextField() {
        if text?.isEmpty == false {
            textField.text = text
            text = nil
        }
    }
    
    private func handleRigthViewStatus(with status: Status?) {
        guard let status = status else { return }
        switch status {
        case .default:
            backgroundView.viewBorderColor = R.Color.gray25
            backgroundView.backgroundColor = R.Color.gray25
            errorLabel.isHidden = true
        case .success:
            backgroundView.viewBorderColor = R.Color.gray25
            backgroundView.backgroundColor = R.Color.gray25
            errorLabel.isHidden = true
        case .failure(let message):
            backgroundView.viewBorderColor = R.Color.red25
            backgroundView.backgroundColor = R.Color.red50
            errorLabel.text = message
            errorLabel.isHidden = message == nil
        case .focus:
            backgroundView.viewBorderColor = R.Color.gray200
            backgroundView.backgroundColor = R.Color.gray25
            errorLabel.isHidden = true
        }
        DispatchQueue.main.async {[weak self] in
            self?.stackView.layoutIfNeeded()
        }
    }
    
    func addToolbarToTextField() {
        // Create the toolbar
        let toolbar = UIToolbar()
        toolbar.sizeToFit()

        // Create a flexible space to push the button to the right
        let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        
        // Create the button
        let doneButton = UIBarButtonItem(title: "Tamam", style: .done, target: self, action: #selector(doneButtonTapped))
        doneButton.tintColor = R.Color.tjkRed
        // Add the items to the toolbar
        toolbar.items = [flexibleSpace, doneButton]

        // Assign the toolbar to the text field's input accessory view
        textField.inputAccessoryView = toolbar
    }
    
    // MARK: - OBJC(s)
    
    @objc
    private func valueChanged() {
        updateFloatingLabel()
        delegate?.textFieldDidChange?(self)
    }
    
    @objc
    private func tappedRightButton() {
        if rightIcon != nil {
            delegate?.didTapRightButton?(self)
        } else {
            self.becomeFirstResponder()
        }
    }
    
    @objc
    private func doneButtonTapped() {
        if let shouldTapped = delegate?.didTapDoneButton?(self) {
            return shouldTapped
        } else {
            textField.endEditing(true)
        }
    }
    
    @objc
    private func placeholderTapped() {
        self.becomeFirstResponder()
    }
}

extension DefaultTextField: UITextFieldDelegate {
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let shouldChange = delegate?.textField?(self, shouldChangeCharactersIn: range, replacementString: string) {
            if !shouldChange {
                updateFloatingLabel()
            }
            return shouldChange
        } else {
            return true
        }
    }
    func textFieldDidEndEditing(_ textField: UITextField) {
        status = .default
        delegate?.textFieldDidEndEditing?(self)
    }
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let shouldReturn = delegate?.textFieldShouldReturn?(self) {
            return shouldReturn
        } else {
            return true
        }
    }
    
    func textFieldDidBeginEditing(_ textField: UITextField) {
        status = .focus
        delegate?.textFieldDidBeginEditing?(self)
    }
}

Upvotes: 0

Views: 31

Answers (0)

Related Questions