Skip to content

Instantly share code, notes, and snippets.

@dobster
Last active June 25, 2023 14:12
Show Gist options
  • Save dobster/3d19901adf4e55eeaeaa74a321da3269 to your computer and use it in GitHub Desktop.
Save dobster/3d19901adf4e55eeaeaa74a321da3269 to your computer and use it in GitHub Desktop.
Expanding Table View Cell to reveal UITextView
//
// ExpandoTableViewCell.swift
// TestExpandoCells
//
import UIKit
protocol ExpandoTableViewCellDelegate: class {
func expandoTableViewCellDidTapCell(_ cell: ExpandoTableViewCell)
func expandoTableViewCell(_ cell: ExpandoTableViewCell, didChangeNotes notes: String)
}
class ExpandoTableViewCell: UITableViewCell, UITextViewDelegate {
@IBOutlet var button: UIButton!
@IBOutlet var smallLayoutConstraint: NSLayoutConstraint!
@IBOutlet var largeLayoutConstraint: NSLayoutConstraint!
@IBOutlet var textView: UITextView!
override func awakeFromNib() {
super.awakeFromNib()
textView.delegate = self
}
weak var delegate: ExpandoTableViewCellDelegate?
var notes: String = "" {
didSet {
textView.text = notes
}
}
var isExpanded: Bool = true {
didSet {
if isExpanded {
smallLayoutConstraint.priority = .defaultLow
largeLayoutConstraint.priority = .defaultHigh
textView.textColor = UIColor.gray
textView.isHidden = false
} else {
textView.isHidden = true
largeLayoutConstraint.priority = .defaultLow
smallLayoutConstraint.priority = .defaultHigh
}
}
}
@IBAction func didTapCell(_ sender: Any) {
delegate?.expandoTableViewCellDidTapCell(self)
}
func textViewDidBeginEditing(_ textView: UITextView) {
textView.textColor = UIColor.black
}
func textViewDidEndEditing(_ textView: UITextView) {
textView.textColor = UIColor.gray
delegate?.expandoTableViewCell(self, didChangeNotes: textView.text ?? "")
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" rowHeight="135" id="KGk-i7-Jjw" customClass="ExpandoTableViewCell" customModule="TestExpandoCells" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="330" height="135"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="KGk-i7-Jjw" id="H2p-sc-9uM">
<rect key="frame" x="0.0" y="0.0" width="330" height="134.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="750" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="y7a-Vx-IPl">
<rect key="frame" x="16" y="11" width="162" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="pb4-od-cH6">
<rect key="frame" x="16" y="39.5" width="298" height="84"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="height" constant="84" id="e8f-0Y-vV4"/>
</constraints>
<string key="text">Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. odioque civiuda.</string>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences" enablesReturnKeyAutomatically="YES"/>
</textView>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Shh-q3-7at">
<rect key="frame" x="268" y="6" width="46" height="30"/>
<state key="normal" title="Button"/>
<connections>
<action selector="didTapCell:" destination="KGk-i7-Jjw" eventType="touchUpInside" id="ghg-1u-4CM"/>
</connections>
</button>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="y7a-Vx-IPl" secondAttribute="bottom" priority="250" constant="8" id="8CB-LD-RlP"/>
<constraint firstItem="Shh-q3-7at" firstAttribute="leading" secondItem="y7a-Vx-IPl" secondAttribute="trailing" constant="90" id="JgC-mw-hdE"/>
<constraint firstAttribute="trailing" secondItem="Shh-q3-7at" secondAttribute="trailing" constant="16" id="Zqf-AA-tIZ"/>
<constraint firstItem="y7a-Vx-IPl" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="b15-bz-CXd"/>
<constraint firstItem="pb4-od-cH6" firstAttribute="leading" secondItem="H2p-sc-9uM" secondAttribute="leadingMargin" id="cPl-CD-TE5"/>
<constraint firstItem="pb4-od-cH6" firstAttribute="top" secondItem="y7a-Vx-IPl" secondAttribute="bottom" priority="750" constant="8" id="feQ-F8-WZJ"/>
<constraint firstItem="pb4-od-cH6" firstAttribute="trailing" secondItem="H2p-sc-9uM" secondAttribute="trailingMargin" id="hBK-nv-gta"/>
<constraint firstItem="Shh-q3-7at" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="top" constant="6" id="m1g-jv-yOE"/>
<constraint firstAttribute="bottom" secondItem="y7a-Vx-IPl" secondAttribute="bottom" priority="250" constant="108" id="obx-Hh-SdV"/>
<constraint firstItem="y7a-Vx-IPl" firstAttribute="top" secondItem="H2p-sc-9uM" secondAttribute="topMargin" id="wDZ-4B-RFo"/>
</constraints>
</tableViewCellContentView>
<viewLayoutGuide key="safeArea" id="njF-e1-oar"/>
<connections>
<outlet property="button" destination="Shh-q3-7at" id="ZMV-rR-DmC"/>
<outlet property="largeLayoutConstraint" destination="obx-Hh-SdV" id="9zq-tt-lCS"/>
<outlet property="smallLayoutConstraint" destination="8CB-LD-RlP" id="JqL-2O-zlw"/>
<outlet property="textView" destination="pb4-od-cH6" id="c6N-ae-wcE"/>
</connections>
<point key="canvasLocation" x="145.59999999999999" y="194.75262368815595"/>
</tableViewCell>
</objects>
</document>
//
// ViewController.swift
// TestExpandoCells
//
import UIKit
struct Model {
let notes: String
var isExpanded: Bool { return notes.isNotEmpty }
static let `default` = Model(notes: "")
}
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, ExpandoTableViewCellDelegate {
@IBOutlet var tableView: UITableView!
var model: [Model] = [Model.default, Model.default, Model.default, Model.default, Model.default, Model.default, Model.default, Model.default, Model.default]
var editingIndexPath: IndexPath?
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
tableView.tableFooterView = UIView(frame: tableView.bounds)
tableView.register(UINib(nibName: "ExpandoTableViewCell", bundle: nil), forCellReuseIdentifier: "Expando Cell")
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Expando Cell", for: indexPath) as! ExpandoTableViewCell
let thisModel = model[indexPath.row]
cell.isExpanded = thisModel.isExpanded || indexPath == editingIndexPath
cell.notes = thisModel.notes
cell.delegate = self
return cell
}
func expandoTableViewCellDidTapCell(_ cell: ExpandoTableViewCell) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
editingIndexPath = indexPath
tableView.reloadRows(at: [indexPath], with: .automatic)
guard let newCell = tableView.cellForRow(at: indexPath) as? ExpandoTableViewCell else { return }
newCell.textView.becomeFirstResponder()
}
func expandoTableViewCell(_ cell: ExpandoTableViewCell, didChangeNotes notes: String) {
guard let indexPath = tableView.indexPath(for: cell) else { return }
let trimmedNotes = notes.trimmingCharacters(in: .whitespaces)
let newModel = Model(notes: trimmedNotes)
model[indexPath.row] = newModel
editingIndexPath = nil
tableView.reloadRows(at: [indexPath], with: .automatic)
}
}
extension Collection {
var isNotEmpty: Bool { return !isEmpty }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment