1

I have a UITableViewController that contains multiple sections. Each section contains rows with custom UITableViewCells. The cell has 4 main elements:

  • Name label
  • Quantity label
  • Check button
  • Profile picture

What I'm struggling with, is how the cells are reused. In this answer, I read that the non-content related things should be done in prepareForReuse and the content-related 'resets' should be done in cellForRowAt. However, I'm unable to figure out how I can do so. At the moment, one main thing seems to be going wrong.

The constraints aren't being 'transferred' correctly

This is the code in my cellForRowAt right now:

        let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskCell
        let currentItem = sections[indexPath.section].items[indexPath.row]
        
        cell.taskNameLabel.text = currentItem.name
        cell.uid = currentItem.uid
        cell.delegate = self
        cell.indexSection = indexPath.section
        cell.indexRow = indexPath.row
        cell.itemID = currentItem.itemID
        cell.items = sections[indexPath.section].items
        cell.quantity = ""
        cell.quantity = currentItem.quantity
        
        if currentItem.quantity != nil && currentItem.quantity != "" && currentItem.quantity != " " {
            cell.quantityLabel.isHidden = false
            cell.quantityLabel.text = currentItem.quantity?.uppercased()
            cell.taskNameLabel.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = false
        } else {
            cell.quantityLabel.isHidden = true
            cell.taskNameLabel.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
        }
        
        if currentItem.uid == Auth.auth().currentUser?.uid {
            cell.profilePicture.isHidden = true
        } else {
            cell.profilePicture.isHidden = false
        }
        
        if currentItem.checked {
            cell.checkBoxOutlet.setBackgroundImage(#imageLiteral(resourceName: "checkBoxFILLED "), for: UIControl.State.normal)
        } else {
            cell.checkBoxOutlet.setBackgroundImage(#imageLiteral(resourceName: "checkBoxOUTLINE "), for: UIControl.State.normal)
        }

When the line cell.taskNameLabel.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true and cell.taskNameLabel.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = false are commented out, the code is rendered like the left simulator: https://www.loom.com/share/fcc332ff862741a8ae346e43820b0b60?from_recorder=1

What happens is that when, for example, row 2 doesn't have a quantity, the quantity on the new row 2 is hidden too (while that cell is supposed to have a quantity). When the line about the constraints is commented out it all works as supposed, but when it is included, the weird situation happened.

How can I make sure that the constraints (center the name label in the Y-axis if the cell does not have a quantity) are prepared for reuse correctly?

So sorry if I'm not being clear, I'm really not sure how to go further and quite frankly, lost.

To the StackOverflow community: many thanks in advance!

Matt
  • 61
  • 6

1 Answers1

1

It sounds like you want output looking something like this:

enter image description here

If so, here's how I would design the cell:

enter image description here

That's:

  • a button
  • a vertical stack view with two labels
  • an image view

The profile image view's Trailing anchor is constrained 16-pts from the content view's Trailing anchor, and it has a CenterY anchor.

The button is constrained Leading: 0, CenterY ... and Top and Bottom both greaterThanOrEqual to 6

The stack view is constrained Leading-to-ButtonTrailing: 8, Trailing-to-ProfileImageViewLeading: -8, CenterY ... and Top and Bottom both greaterThanOrEqual to 4

With that, we're telling auto-layout to make the cell height:

  • at least 8-points taller than the stack view
  • at least 12-pts taller than the button

When we have a Quantity, both labels will be filled and not hidden. So the stack view will be taller than the button and its Top/Bottom constraints will take priority.

If we don't have a quantity, the Name label will be visible but the Quantity label will be hidden. So, the button will be taller, and its Top/Bottom constraints will take priority.

In both cases, everything will remain centered vertically in the cell.

Here is a simplified version of the cell class:

class TaskCell: UITableViewCell {

    @IBOutlet var checkBoxOutlet: UIButton!
    @IBOutlet var taskNameLabel: UILabel!
    @IBOutlet var quantityLabel: UILabel!
    @IBOutlet var profilePicture: UIImageView!
    
    func fillData(_ task: Task, profileImage: UIImage?) -> Void {
        
        let sysName: String = task.checked ? "largecircle.fill.circle" : "circle"
        if let img = UIImage(systemName: sysName) {
            checkBoxOutlet.setBackgroundImage(img, for: .normal)
        }
        
        taskNameLabel.text = task.name.uppercased()
        
        // make sure quantity is not " "
        let q = task.quantity.trimmingCharacters(in: .whitespacesAndNewlines)
        quantityLabel.text = q.uppercased()
        quantityLabel.isHidden = q == ""
        
        profilePicture.image = profileImage
        profilePicture.isHidden = profileImage == nil
        
    }
}

Here's the source to the storyboard so you can inspect the cell layout:

<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="0rh-3w-bzr">
    <device id="retina4_7" orientation="portrait" appearance="light"/>
    <dependencies>
        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17125"/>
        <capability name="System colors in document resources" minToolsVersion="11.0"/>
        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
    </dependencies>
    <scenes>
        <!--Example Table View Controller-->
        <scene sceneID="RP6-4O-gi2">
            <objects>
                <tableViewController id="0rh-3w-bzr" customClass="ExampleTableViewController" customModule="DelMe" customModuleProvider="target" sceneMemberID="viewController">
                    <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="9oA-oN-4sp">
                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                        <color key="backgroundColor" systemColor="systemBackgroundColor"/>
                        <prototypes>
                            <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="taskCell" rowHeight="104" id="0OE-gK-JaO" customClass="TaskCell" customModule="DelMe" customModuleProvider="target">
                                <rect key="frame" x="0.0" y="28" width="375" height="104"/>
                                <autoresizingMask key="autoresizingMask"/>
                                <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="0OE-gK-JaO" id="GeB-v3-7H5">
                                    <rect key="frame" x="0.0" y="0.0" width="375" height="104"/>
                                    <autoresizingMask key="autoresizingMask"/>
                                    <subviews>
                                        <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Rrf-x2-kgv">
                                            <rect key="frame" x="16" y="36.5" width="32" height="31"/>
                                            <constraints>
                                                <constraint firstAttribute="width" constant="32" id="aBr-zc-Hi1"/>
                                                <constraint firstAttribute="width" secondItem="Rrf-x2-kgv" secondAttribute="height" multiplier="1:1" id="t6J-WJ-4Dq"/>
                                            </constraints>
                                            <state key="normal" backgroundImage="circle" catalog="system"/>
                                        </button>
                                        <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="ugR-45-YpU">
                                            <rect key="frame" x="56" y="31" width="255" height="42.5"/>
                                            <subviews>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="d1t-1z-Pyt">
                                                    <rect key="frame" x="0.0" y="0.0" width="255" height="20.5"/>
                                                    <color key="backgroundColor" red="0.55634254220000001" green="0.97934550050000002" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                    <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                                <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="efT-dG-UOP">
                                                    <rect key="frame" x="0.0" y="24.5" width="255" height="18"/>
                                                    <color key="backgroundColor" red="0.99953407049999998" green="0.98835557699999999" blue="0.47265523669999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                                    <fontDescription key="fontDescription" type="system" pointSize="15"/>
                                                    <nil key="textColor"/>
                                                    <nil key="highlightedColor"/>
                                                </label>
                                            </subviews>
                                        </stackView>
                                        <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="Hlp-8o-qHO">
                                            <rect key="frame" x="319" y="40" width="24" height="24"/>
                                            <color key="tintColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
                                            <constraints>
                                                <constraint firstAttribute="width" constant="24" id="4ax-ud-MaN"/>
                                                <constraint firstAttribute="width" secondItem="Hlp-8o-qHO" secondAttribute="height" multiplier="1:1" id="V9i-E8-rqH"/>
                                            </constraints>
                                        </imageView>
                                    </subviews>
                                    <constraints>
                                        <constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="ugR-45-YpU" secondAttribute="bottom" constant="4" id="5Tm-8S-wzZ"/>
                                        <constraint firstItem="Rrf-x2-kgv" firstAttribute="top" relation="greaterThanOrEqual" secondItem="GeB-v3-7H5" secondAttribute="topMargin" constant="6" id="D7D-hZ-deP"/>
                                        <constraint firstItem="ugR-45-YpU" firstAttribute="leading" secondItem="Rrf-x2-kgv" secondAttribute="trailing" constant="8" id="F7l-d8-Raf"/>
                                        <constraint firstItem="ugR-45-YpU" firstAttribute="centerY" secondItem="GeB-v3-7H5" secondAttribute="centerY" id="Gzg-V9-jnW"/>
                                        <constraint firstItem="Rrf-x2-kgv" firstAttribute="centerY" secondItem="GeB-v3-7H5" secondAttribute="centerY" id="Kmx-OQ-LVg"/>
                                        <constraint firstItem="ugR-45-YpU" firstAttribute="top" relation="greaterThanOrEqual" secondItem="GeB-v3-7H5" secondAttribute="topMargin" constant="4" id="LVP-0M-KbC"/>
                                        <constraint firstAttribute="trailingMargin" secondItem="Hlp-8o-qHO" secondAttribute="trailing" constant="16" id="QpT-89-Kci"/>
                                        <constraint firstAttribute="bottomMargin" relation="greaterThanOrEqual" secondItem="Rrf-x2-kgv" secondAttribute="bottom" constant="6" id="STP-yS-UR0"/>
                                        <constraint firstItem="Rrf-x2-kgv" firstAttribute="leading" secondItem="GeB-v3-7H5" secondAttribute="leadingMargin" id="Sum-If-WiN"/>
                                        <constraint firstItem="Hlp-8o-qHO" firstAttribute="centerY" secondItem="GeB-v3-7H5" secondAttribute="centerY" id="h42-Iy-CAV"/>
                                        <constraint firstItem="Hlp-8o-qHO" firstAttribute="leading" secondItem="ugR-45-YpU" secondAttribute="trailing" constant="8" id="wTj-7Z-ZCr"/>
                                    </constraints>
                                </tableViewCellContentView>
                                <connections>
                                    <outlet property="checkBoxOutlet" destination="Rrf-x2-kgv" id="EVr-hk-wdw"/>
                                    <outlet property="profilePicture" destination="Hlp-8o-qHO" id="HjC-EX-P2D"/>
                                    <outlet property="quantityLabel" destination="efT-dG-UOP" id="vzF-FI-zX3"/>
                                    <outlet property="taskNameLabel" destination="d1t-1z-Pyt" id="kSQ-K6-gqc"/>
                                </connections>
                            </tableViewCell>
                        </prototypes>
                        <connections>
                            <outlet property="dataSource" destination="0rh-3w-bzr" id="Iqt-W7-ziA"/>
                            <outlet property="delegate" destination="0rh-3w-bzr" id="CJ8-AF-j5x"/>
                        </connections>
                    </tableView>
                </tableViewController>
                <placeholder placeholderIdentifier="IBFirstResponder" id="jyI-jf-7cF" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
            </objects>
            <point key="canvasLocation" x="183.19999999999999" y="180.35982008995504"/>
        </scene>
    </scenes>
    <resources>
        <image name="circle" catalog="system" width="128" height="121"/>
        <systemColor name="systemBackgroundColor">
            <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
        </systemColor>
    </resources>
</document>
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Wow. Thank you so much for taking the time to answer this so clearly and detailed. I'm going to look at it right away! Thank you so much :) – Matt Oct 23 '20 at 16:02
  • I hope the mods allow this thank you comment but I wanted to say the following. You are an absolute legend. You have no idea how long I've spent trying to fix this annoying bug and how frustrated I was not being able to figure it out. Your solution works perfectly, and I'm extremely thankful that you took the time to explain this so clearly and detailed. Thank you so, so much. – Matt Oct 23 '20 at 19:02
  • Hey @DonMag, just following up with an issue I have with this code - the moveRow animation disappears now. Before, this `self.tableView.moveRow(at: IndexPath(row: indexRow, section: indexSection), to: IndexPath(row: lastRow, section: indexSection))` animated the row change, but now it just kind of snaps into place. [This is what I mean](https://www.dropbox.com/s/yeqya3ibiyser82/Screen%20Recording%202020-11-03%20at%2022.55.33.mov?dl=0). Do you have an idea as to why this is happening? Many thanks, - Matt. – Matt Nov 03 '20 at 21:56
  • 1
    @Matt - difficult to tell from that video. You'll have to show what code you're running. – DonMag Nov 03 '20 at 22:34
  • Thank you for your reply; [this](https://github.com/mattttis/Carrot) is the GitHub project, is that okay? The 'moveRow' is in TableViewController.swift at line 393. – Matt Nov 03 '20 at 22:39
  • @Matt - it has nothing to do with the cell design itself. After a quick review of your code... if you comment this line (in `changeButton()` in `TableViewController` class): `self.tableView.moveRow(at: IndexPath(row: indexRow, section: indexSection), to: IndexPath(row: lastRow, section: indexSection))` the row is moved anyway. That tells me that something is triggering a `reloadData()` -- *probably* when you call `itemRef.updateData...` (just above that) it automatically reloads the table. – DonMag Nov 04 '20 at 16:01