2

I have a bunch of UITextFields living inside a collection view in different cells (around 7). I'm looking for a way to delegate the responsibility of validating these fields to an internal service class. Is there a way in which I can do this without causing controller bloat?

What I've partially tried is to create an object and then assign the Textfield delegates to this object. However I struggled on linking the two together (able to access the textField.text property to validate and add the output into a struct).

Is there a cleaner, more efficient way or am I looking at a long Controller?

Thanks

user7684436
  • 697
  • 14
  • 39
  • 1
    You are on the right lines. It will probably help for you to share your code here by editing your answer in order that people can help you more. It is very hard to answer your question without knowing the code you have in place. – lukkea Apr 01 '17 at 18:44

1 Answers1

4

Features

  • Save data from TextFields in cell
  • Detect coordinates of editing TextField
  • Locate TextFields handler in ViewController (MVC)
  • Hide keyboard when scroll

Details

xCode 8.3, Swift 3.1

Full Example

ViewController.swift

import UIKit

fileprivate var textFieldsTexts = [IndexPath:String]()

class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!

    override func viewDidLoad() {
        super.viewDidLoad()
        setupCollectionView()
    }
}

extension ViewController: UICollectionViewDataSource {
    func setupCollectionView() {
        collectionView.dataSource = self
        collectionView.delegate = self
    }

    func numberOfSections(in collectionView: UICollectionView) -> Int {
        return 1
    }

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 100
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath) as! CollectionViewCell
        cell.textField.placeholder = "\(indexPath)"
        cell.delegate = self
        return cell
    }
}

extension ViewController: UICollectionViewDelegate {
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        let cell = cell as! CollectionViewCell
        cell.textField.text = textFieldsTexts[indexPath]
    }

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

extension ViewController: CollectionViewCellDelegate {

    func collectionViewCell(valueChangedIn textField: UITextField, delegatedFrom cell: CollectionViewCell) {
        if let indexPath = collectionView.indexPath(for: cell), let text = textField.text {
            print("textField text: \(text) from cell: \(indexPath))")
            textFieldsTexts[indexPath] = text
        }
    }

    func collectionViewCell(textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String, delegatedFrom cell: CollectionViewCell)  -> Bool {
        print("Validation action in textField from cell: \(String(describing: collectionView.indexPath(for: cell)))")
        return true
    }

}

CollectionViewCell.swift

import UIKit

class CollectionViewCell: UICollectionViewCell {

    @IBOutlet weak var textField: UITextField!
    var delegate: CollectionViewCellDelegate?

    override func awakeFromNib() {
        super.awakeFromNib()
        textField.delegate = self
    }

    @IBAction func valueChanged(_ sender: UITextField) {
        delegate?.collectionViewCell(valueChangedIn: textField, delegatedFrom: self)
    }
}

extension CollectionViewCell: UITextFieldDelegate {

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let delegate = delegate {
            return delegate.collectionViewCell(textField: textField, shouldChangeCharactersIn: range, replacementString: string, delegatedFrom: self)
        }
        return true
    }
}

CollectionViewCellDelegate.swift

import UIKit

protocol CollectionViewCellDelegate {
    func collectionViewCell(valueChangedIn textField: UITextField, delegatedFrom cell: CollectionViewCell)
    func collectionViewCell(textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String, delegatedFrom cell: CollectionViewCell)  -> Bool
}

Main.storyboard

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12118" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
    <device id="retina4_7" orientation="portrait">
        <adaptation id="fullscreen"/>
    </device>
    <dependencies>
        <deployment identifier="iOS"/>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12086"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--View Controller-->
        <scene sceneID="tne-QT-ifu">
            <objects>
                <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="stackoverflow_43143119" customModuleProvider="target" sceneMemberID="viewController">
                    <layoutGuides>
                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
                    </layoutGuides>
                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <subviews>
                            <collectionView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" translatesAutoresizingMaskIntoConstraints="NO" id="KWt-KV-A2D">
                                <rect key="frame" x="0.0" y="28" width="375" height="639"/>
                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
                                <collectionViewFlowLayout key="collectionViewLayout" minimumLineSpacing="10" minimumInteritemSpacing="10" id="WoG-le-dkA">
                                    <size key="itemSize" width="100" height="50"/>
                                    <size key="headerReferenceSize" width="0.0" height="0.0"/>
                                    <size key="footerReferenceSize" width="0.0" height="0.0"/>
                                    <inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
                                </collectionViewFlowLayout>
                                <cells>
                                    <collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="CollectionViewCell" id="w5c-mF-XjG" customClass="CollectionViewCell" customModule="stackoverflow_43143119" customModuleProvider="target">
                                        <rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
                                        <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
                                        <view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
                                            <rect key="frame" x="0.0" y="0.0" width="100" height="50"/>
                                            <autoresizingMask key="autoresizingMask"/>
                                            <subviews>
                                                <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="text" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="HAy-QM-Eqi">
                                                    <rect key="frame" x="0.0" y="10" width="100" height="30"/>
                                                    <nil key="textColor"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="14"/>
                                                    <textInputTraits key="textInputTraits"/>
                                                    <connections>
                                                        <action selector="valueChanged:" destination="w5c-mF-XjG" eventType="editingChanged" id="qDf-D2-X1l"/>
                                                    </connections>
                                                </textField>
                                            </subviews>
                                        </view>
                                        <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="calibratedWhite"/>
                                        <constraints>
                                            <constraint firstItem="HAy-QM-Eqi" firstAttribute="centerY" secondItem="w5c-mF-XjG" secondAttribute="centerY" id="JCH-09-Hsr"/>
                                            <constraint firstItem="HAy-QM-Eqi" firstAttribute="leading" secondItem="w5c-mF-XjG" secondAttribute="leading" id="YvF-Qw-sOW"/>
                                            <constraint firstAttribute="trailing" secondItem="HAy-QM-Eqi" secondAttribute="trailing" id="kPK-6X-Aug"/>
                                        </constraints>
                                        <connections>
                                            <outlet property="textField" destination="HAy-QM-Eqi" id="3tf-0f-agX"/>
                                        </connections>
                                    </collectionViewCell>
                                </cells>
                            </collectionView>
                        </subviews>
                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                        <constraints>
                            <constraint firstItem="KWt-KV-A2D" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="8" symbolic="YES" id="5Fe-R9-7li"/>
                            <constraint firstItem="KWt-KV-A2D" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="Rjm-Ij-0Pe"/>
                            <constraint firstAttribute="trailing" secondItem="KWt-KV-A2D" secondAttribute="trailing" id="kZ0-HK-x0Z"/>
                            <constraint firstItem="KWt-KV-A2D" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="zQM-Za-bkN"/>
                        </constraints>
                    </view>
                    <connections>
                        <outlet property="collectionView" destination="KWt-KV-A2D" id="SR1-Cn-p5v"/>
                    </connections>
                </viewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="133.59999999999999" y="137.18140929535232"/>
        </scene>
    </scenes>
</document>

Result

enter image description here

Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127