LoginSignup
2
3

More than 3 years have passed since last update.

UITableView とTextField備忘録 Outlet cannot be connected to repeating content

Posted at

StoryboardからUITableVIewのCellの中でTextFiledなどを配置して
ViewControllerへ連携したら、ビルドする時にエラーになりました:
Outlet cannot be connected to repeating content

Cellは繰り返し使用されるオブジェクトであるため、直接Outlet接続できないらしい、
解決方法としてサブクラスで実装、つまりXibファイルで実装になります。
こちらの記事を参考しました。

TableViewとTexFieldの組み合わせ実装の備忘録としてメモしておきます。

1.セールの中身を一括で宣言

enum Section: CaseIterable {
    case a
    case t

    var text:[String]? {
        switch self {
        case .a:
            return ["A","a","あ"]
        default:
            return nil
        }
    }

    var identifiers:[String]{
        switch  self {
        case .a:
            return ["acell","acell","acell"]
        case .t:
            return ["tcell","tcell","tcell"]
        }
    }

    var header:String{
        switch self {
        case .a:
            return "aHeader"
        case .t:
            return "tHeader"
        }
    }

    var indicator:Bool{
        switch self {
        case .a:
            return true
        case .t:
            return false
        }
    }
}

2.ViewControllerでデータを反映

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return Section.allCases.count
    }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return Section.allCases[section].identifiers.count
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return Section.allCases[section].header
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let section = Section.allCases[indexPath.section]
        let cell = tableView.dequeueReusableCell(withIdentifier: section.identifiers[indexPath.row], for: indexPath)
        return cell
    }
}

extension ViewController: UITableViewDelegate{

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 45.0
    }

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let section = Section.allCases[indexPath.section]
        if section.indicator {
            cell.accessoryType = .disclosureIndicator
        }

        if section.identifiers[indexPath.row] == "acell"{
            cell.textLabel?.text = section.text![indexPath.row]
        }
    }
}

3.二種類のCellを作る
1)Cellの中にLableを配置してTextを表示する”acell”
2)Cellの中にTextFieldを配置して入力できるようにする”tcell”

4.Xibファイルで作ったCellを登録

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        let nib = UINib.init(nibName: "TextFieldCell", bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: "tcell")
    }
}

5.StoryBoardでDataSourceとDelegateの連携も忘れずに
この段階ではこのようなTableViewになっています。
Simulator Screen Shot - iPhone 8 - 2021-03-01 at 14.20.09.png

6.Keyboardを閉じる処理
TextFieldで入力中、TableViewをスクロールすると、Keyboardを閉じる処理

func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    self.view.endEditing(true)
}

7.入力箇所が常に画面の真ん中に表示するように処理
例えば、画面の下側のCellのTextFieldをタップしたら、TableView全体を上へスクロール、
入力中のCellが常に真ん中に表示するようには、2ステップです。
1)UITextFieldDelegateのBeginEditingが入力始めると(Keyboardが表示すると)呼ばれるDelegateで
 TableView.ScrollToRowを実装
2)Keyboardの高さ分を画面全体を上へ持ち上げる実装

1)はUITextFieldのXibクラスでDelegateを作り、使用します。delegate = selfを忘れずに

protocol TestTextFieldDelegate: class {
    func beginEditing(_ cell:TextFieldCell)
}

extension TextFieldCell: UITextFieldDelegate { 
    func textFieldDidBeginEditing(_ textField: UITextField) {
        self.textField!.becomeFirstResponder()
        self.delegate?.beginEditing(self)
    }
}
extension ViewController: TestTextFieldDelegate {
    func beginEditing(_ cell: TextFieldCell) {
        let index = tableView.indexPath(for: cell)
        tableView.scrollToRow(at: index!, at: .middle, animated: true)
    }
}

2)はVieControllerの方で実装

class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var tableBottomLayout: NSLayoutConstraint!

    override func viewDidLoad() {
        super.viewDidLoad()
        let nib = UINib.init(nibName: "TextFieldCell", bundle: nil)
        tableView.register(nib, forCellReuseIdentifier: "tcell")
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        regsiterKeyboardNotification()
    }

    private func regsiterKeyboardNotification(){
        let notification = NotificationCenter.default
        notification.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
        notification.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
    }

    @objc func keyboardWillShow(_ notification:Notification?){
        guard let userInfo = notification?.userInfo,
              let keyboradInfo = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
              let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else{
            return
        }
        let keyboardHieght = keyboradInfo.cgRectValue.size.height
        self.tableBottomLayout.constant = keyboardHieght

        UIView.animate(withDuration: duration){
            self.view.layoutIfNeeded()
        }
    }

    @objc func keyboardWillHide(_ notification:Notification?){
        guard let userInfo = notification?.userInfo,
              let duration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else{
            return
        }

        self.tableBottomLayout.constant = 0

        UIView.animate(withDuration: duration){
            self.view.layoutIfNeeded()
        }
    }
}

tableBottomLayoutですが、SotryBoardはこのようになっています。
FirstItemとSecondItemが逆だとうまくいかないみたい。
スクリーンショット 2021-03-01 15.15.03.png

8.入力内容制限
1)xx文字以上入力できない
StoryBoardからTextFieldのAction:EditingChangedをXibファイルへ接続して、実装します。

@IBAction func textChanged(_ sender: UITextField) {
    checkMaxLength()
}

private func checkMaxLength(){
    var text = textField.text
    guard text != nil else{
        return
    }
    if text!.count > 5 {
        text = String(text!.prefix(5))
    }
    if self.textField.text != text{
        self.textField.text = text
    }
}

2)特殊の文字を入れないようにする
例えば、英数字記号以外を入力できないようにするには以下で実装します

extension String {

    var ns: NSString{
        return self as NSString
    }

    func deleteMatched(parttern:String?)-> String{
        guard let parttern = parttern else{
            return self
        }

        guard let regex = try? NSRegularExpression(pattern: parttern, options:
                                                    NSRegularExpression.Options.caseInsensitive) else { return self }
        let range = NSRange(location: 0, length: self.ns.length)
        let modStirng = regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "")
        return modStirng
    }
}

入力できない文字列を宣言

let NONAlphanumericAndSymbols = "[^a-zA-Z0-9!#&'()*+,/:;=?@\\[\\]\\-\\._~]+"
private func checkMaxLength(){
    var text = textField.text
    guard text != nil else{
        return
    }

 ///こちらを追加
    text = text?.deleteMatched(parttern: self.NONAlphanumericAndSymbols)

    if text!.count > 5 {
        text = String(text!.prefix(5))
    }
    if self.textField.text != text{
        self.textField.text = text
    }
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3