How to Display Nested Array Data Inside Uitableview Properly with Multiple Level of Sections

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

Sample Image

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:

Sample Image

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:

Sample Image

and after scrolling down to the Trucks section:

Sample Image

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



Leave a reply



Submit