iPhone UITableView Nested Sections
You got it. A UITableView
really isn't designed to show more than two levels of a hierarchy, as sections and rows. If you want to show more than two levels, a "drill-down" approach used in most (all?) iOS apps, where tapping a row presents another UITableView
on the navigation stack. (As you say.)
There are lots of Apple sample code projects that use this design pattern.
Edit: just checked and DrillDownSave is a good example, as is SimpleDrillDown.
Swift - Populating TableViews with multiple sections from multidimensional array
First off: A dictionary is a poor choice for storing table view contents. Dictionaries are inherently unordered, so you have to keep sorting the keys. If your data gets beyond a small number of items that sort process will take appreciable time.
If you are going to use a dictionary anyway you should refactor your code so that you keep the sorted keys and use them over and over unless the dictionary changes. I would make it so that you add a setter method to the dictionary and always use that setter to change the dictionary. The setter method would regenerate the sorted keys. That way you only have to sort the keys if the dictionary changes. (But better to get rid of the dictionary entirely.)
I would suggest creating a Section
object that contains a sectionTitle String
and a sectionEntries
Array. Then make the table view data an array of Section objects.
However, since you have a dictionary, I'll show you how to make that code work.
You need help with your cellForRowAtIndexPath
method. You are almost there. You just need to fetch the appropriate entry in your data structure using the indexPath section and row. Something like this:
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("BufferCell",
forIndexPath: indexPath)
let section = indexPath.section
let row = indexPath.row
let categoryKey:String = categoryList[section]
let aCategoryEntry:[String] = categoryDict[categoryKey] as! [String]
let anObject = aCategoryEntry[row] //
let cell.textLabel.text = anObject
return cell
}
How do I populate two sections in a tableview with two different arrays using swift?
TableView Cells
You could use a multidimensional array. For example:
let data = [["0,0", "0,1", "0,2"], ["1,0", "1,1", "1,2"]]
For the number of sections use:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return data.count
}
Then, to specify the number of rows in each section use:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data[section].count
}
Finally, you need to setup your cells:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellText = data[indexPath.section][indexPath.row]
// Now do whatever you were going to do with the title.
}
TableView Headers
You could again use an array, but with just one dimension this time:
let headerTitles = ["Some Data 1", "KickAss"]
Now to set the titles for the sections:
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section < headerTitles.count {
return headerTitles[section]
}
return nil
}
The code above checks to see there's a title for that section and returns it, otherwise nil
is returned. There won't be a title if the number of titles in headerTitles
is smaller than the number of arrays in data
.
The Result
Swift iOS how to handle JSON nested array in object class?
use QuickType to get the model this is quite helpful. you will be getting the model easily(suggestion). I think you are using the wrong model.
if your Base model struct is correct it should be something like this
struct Coffie: Codable {
let id: String
let types: [TypeElement]
let sizes: [Size]
let extras: [Extra]
enum CodingKeys: String, CodingKey {
case id
case types, sizes, extras
}
}
from here itself you can get the type and sizes with corresponded id's you can filter size values from sizes array
Multi Header Sections in UITableView Swift
You can do this fairly easily with a single Cell Prototype:
I've added a single label, constrained on all 4 sides (use margins).
You'll notice one of the constraints is not like the others - Label Leading
- because I connected that as an @IBOutlet
. When I set the cell data, I change the label background color, the .constant
of the leading constraint, and the .selectionStyle
based on it being a "Brand" row or a "Model" row:
enum VehicleType {
case car, truck
}
struct Vehicle {
var type: VehicleType = .car
var brand: String = ""
var model: String = ""
}
class MyCustomCell: UITableViewCell {
@IBOutlet var theLabel: UILabel!
@IBOutlet var labelLeading: NSLayoutConstraint!
func setData(_ v: Vehicle) -> Void {
if v.model == "" {
theLabel.text = v.brand
theLabel.textColor = .darkGray
theLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
labelLeading.constant = 0
selectionStyle = .none
} else {
theLabel.text = v.model
theLabel.textColor = .black
theLabel.backgroundColor = .clear
labelLeading.constant = 16
selectionStyle = .default
}
}
}
In this example, I determine if it's a "Brand" row/cell if the "Model" name is an empty string.
Here's how it can look:
and after scrolling down to the Trucks section:
If you want other appearance differences between the Brand and Model rows, you can handle those in the same .setData()
function.
Here is a complete example:
enum VehicleType {
case car, truck
}
struct Vehicle {
var type: VehicleType = .car
var brand: String = ""
var model: String = ""
}
class MyCustomCell: UITableViewCell {
@IBOutlet var theLabel: UILabel!
@IBOutlet var labelLeading: NSLayoutConstraint!
func setData(_ v: Vehicle) -> Void {
if v.model == "" {
theLabel.text = v.brand
theLabel.textColor = .darkGray
theLabel.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
labelLeading.constant = 0
selectionStyle = .none
} else {
theLabel.text = v.model
theLabel.textColor = .black
theLabel.backgroundColor = .clear
labelLeading.constant = 16
selectionStyle = .default
}
}
}
class MultiSectionViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
@IBOutlet var tableView: UITableView!
// for simulating getting the data
let activityView = UIActivityIndicatorView(style: .large)
// will contain an array of Cars and an array of Trucks
var dataArray: [[Vehicle]] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// empty view as footer so we don't see blank rows
tableView.tableFooterView = UIView()
}
override func viewDidAppear(_ animated: Bool) {
self.simulateGetData()
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let viewContainer = UIView()
viewContainer.backgroundColor = UIColor.lightGray
let labelHeader = UILabel()
labelHeader.textColor = UIColor.white
if section == 0 {
labelHeader.text = "Cars "
}
if section == 1 {
labelHeader.text = "Trucks"
}
viewContainer.addSubview(labelHeader)
labelHeader.autoresizingMask = [.flexibleWidth, .flexibleHeight]
labelHeader.frame = viewContainer.frame
return viewContainer
}
func numberOfSections(in tableView: UITableView) -> Int {
return dataArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCustomCell", for: indexPath) as! MyCustomCell
let vehicle: Vehicle = dataArray[indexPath.section][indexPath.row]
cell.setData(vehicle)
return cell
}
func simulateGetData() -> Void {
// show the "spinner"
view.addSubview(activityView)
activityView.center = CGPoint(x: tableView.center.x, y: tableView.frame.origin.y + 80)
activityView.startAnimating()
// simulate it taking 2 seconds to get the data
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.parseData(self.remoteData)
}
}
func parseData(_ str: String) -> Void {
var fullList: [Vehicle] = []
// split retrieved string into lines
let linesArray: [String] = str.components(separatedBy: "\n")
linesArray.forEach { line in
// split this line
let a: [String] = line.components(separatedBy: ",")
fullList.append(Vehicle(type: a[0] == "car" ? .car : .truck, brand: a[1], model: a[2]))
}
// get the cars
var cars: [Vehicle] = fullList.filter { $0.type == .car }
// get list of car brands
let carBrands = Set((cars).compactMap { $0.brand })
// for each brand, append a Vehicle with Brand but no Model
carBrands.forEach { brand in
cars.append(Vehicle(type: .car, brand: brand, model: ""))
}
// sort cars by brand / model
cars.sort {
($0.brand, $0.model) <
($1.brand, $1.model)
}
// get the trucks and sort by brand / model
var trucks: [Vehicle] = fullList.filter { $0.type == .truck }
// get list of trueck brands
let truckBrands = Set((trucks).compactMap { $0.brand })
// for each brand, append a Vehicle with Brand but no Model
truckBrands.forEach { brand in
trucks.append(Vehicle(type: .truck, brand: brand, model: ""))
}
// sort trucks by brand / model
trucks.sort {
($0.brand, $0.model) <
($1.brand, $1.model)
}
// fill our dataArray
dataArray.append(cars)
dataArray.append(trucks)
// remove the spinner
activityView.stopAnimating()
activityView.removeFromSuperview()
// reload the table
tableView.reloadData()
}
let remoteData: String = """
car,Chevrolet,Camaro
car,Chevrolet,Corvette
car,Chevrolet,Impala
car,Chevrolet,Malibu
car,Chevrolet,Sonic
truck,Chevrolet,Colorado
truck,Chevrolet,Silverado
car,Ford,EcoSport
car,Ford,Edge
car,Ford,Escape
car,Ford,Expedition
car,Ford,Fusion
car,Ford,Mustang
truck,Ford,F-150
truck,Ford,F-250
truck,Ford,F-350
car,Toyota,4Runner
car,Toyota,Avalon
car,Toyota,Camry
car,Toyota,Corolla
car,Toyota,Highlander
car,Toyota,Prius
car,Toyota,Rav4
truck,Toyota,Tacoma
truck,Toyota,Tundra
"""
}
and the source for the Storyboard I used with the Prototype cell:
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="wBJ-BC-ngb">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Multi Section View Controller-->
<scene sceneID="vJm-85-LPr">
<objects>
<viewController id="wBJ-BC-ngb" customClass="MultiSectionViewController" customModule="MiniScratch" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="pxy-Ko-DBo">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="UEv-mW-XVy">
<rect key="frame" x="40" y="100" width="295" height="527"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="myCustomCell" id="rtS-G4-74c" customClass="MyCustomCell" customModule="MiniScratch" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="295" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="rtS-G4-74c" id="39C-jc-tSh">
<rect key="frame" x="0.0" y="0.0" width="295" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<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="KlT-QG-nQC">
<rect key="frame" x="15" y="11" width="265" height="21.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="KlT-QG-nQC" secondAttribute="trailing" id="EpN-7X-Ue5"/>
<constraint firstAttribute="bottomMargin" secondItem="KlT-QG-nQC" secondAttribute="bottom" id="WK9-gS-0S3"/>
<constraint firstItem="KlT-QG-nQC" firstAttribute="top" secondItem="39C-jc-tSh" secondAttribute="topMargin" id="hkl-1J-cH5"/>
<constraint firstItem="KlT-QG-nQC" firstAttribute="leading" secondItem="39C-jc-tSh" secondAttribute="leadingMargin" id="zza-OX-VlC"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="labelLeading" destination="zza-OX-VlC" id="FIW-Qy-k9n"/>
<outlet property="theLabel" destination="KlT-QG-nQC" id="CX2-4G-IlT"/>
</connections>
</tableViewCell>
</prototypes>
</tableView>
</subviews>
<color key="backgroundColor" red="1" green="0.83234566450000003" blue="0.47320586440000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="nNy-q0-wea" firstAttribute="bottom" secondItem="UEv-mW-XVy" secondAttribute="bottom" constant="40" id="Je2-cL-xF0"/>
<constraint firstItem="UEv-mW-XVy" firstAttribute="top" secondItem="nNy-q0-wea" secondAttribute="top" constant="100" id="Wne-7o-FQB"/>
<constraint firstItem="nNy-q0-wea" firstAttribute="trailing" secondItem="UEv-mW-XVy" secondAttribute="trailing" constant="40" id="h4R-46-NsF"/>
<constraint firstItem="UEv-mW-XVy" firstAttribute="leading" secondItem="nNy-q0-wea" secondAttribute="leading" constant="40" id="hUZ-2G-yQI"/>
</constraints>
<viewLayoutGuide key="safeArea" id="nNy-q0-wea"/>
</view>
<connections>
<outlet property="tableView" destination="UEv-mW-XVy" id="pMr-41-iIc"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="mYD-E0-bHz" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="52" y="-68"/>
</scene>
</scenes>
</document>
Related Topics
Selector to Get Indexpath Uicollectionview Swift 3.0
Input Field iOS Safari Bug - Can't Type in Any Text
iOS 7 Parallax Effect in My View Controller
Add Swipe to Delete Uitableviewcell
Convert Uiimage to Nsdata and Convert Back to Uiimage in Swift
Is iOS Carplay API Public? How to Integrate Carplay
Ios7 Status Bar Hide/Show on Select Controllers
Find List of Local Notification the App Has Already Set
Uiimagepickercontroller Crashes App | Swift3, Xcode8
How to Create Otp Verification Screen and Detect Delete Backward on Multiple Uitextfield Is Swift
How Is Possible That "Display: Flex" and "Align-Items: Center" Do Not Work Anymore on My Iphone
Xcode Process Launch Failed: Security
How to Draw Border Around a Uilabel
How to Set the Full Width of Separator in Uitableview
Dynamic Size Uicollectionview Cell
How to Set Cmake_C_Compiler and Cmake_Cxx_Compiler for Building Assimp for iOS