首页 > 解决方案 > UITableView Swift 中的多标题部分

问题描述

以下代码成功填充了一个表格,如UITableView 1图像所示。我想做的是添加一个额外的级别,看起来像UITableView 2图像。

如UITableView 2图像所示,创建多级部分的最佳方法是什么?

仅供参考 - 我尝试按照线程的说明进行操作,但无法使其正常工作。

代码

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    @IBOutlet weak var myTable: UITableView!
    
    let sections = [["Mustang", "Model S"],["F-150", "Cybertruck"]]

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

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let viewContainer = UIView(frame: CGRect(x:0, y:0, width: myTable.frame.width, height: 40))
        viewContainer.backgroundColor = UIColor.lightGray
        let labelHeader = UILabel(frame: CGRect(x:0, y:0, width: 200, height: 30))

        labelHeader.textColor = UIColor.white
        if section == 0{
            labelHeader.text = "Cars "
        }
        if section == 1{
            labelHeader.text =  "Trucks"
        }
        viewContainer.addSubview(labelHeader)
        return viewContainer
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return sections.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0{
            return sections[section].count
        }
        return sections[section].count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCustomCell", for: indexPath) as! MyCustomCell
        if indexPath.section == 0{
            cell.textLabel?.text = sections[indexPath.section][indexPath.row]
        }
        if indexPath.section == 1{
            cell.textLabel?.text = sections[indexPath.section][indexPath.row]
        }
        return cell
    }
}

图片

在此处输入图像描述

标签: iosswiftuitableview

解决方案


您可以使用单个 Cell Prototype 轻松完成此操作:

在此处输入图像描述

我添加了一个标签,限制在所有 4 个边上(使用边距)。

您会注意到其中一个约束与其他约束不同Label Leading- 因为我将其连接为@IBOutlet. 当我设置单元格数据时,我更改标签背景颜色、.constant前导约束的颜色以及.selectionStyle基于它是“品牌”行或“模型”行:

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
        }

    }
    
}

在此示例中,如果“型号”名称为空字符串,我将确定它是否为“品牌”行/单元格。

这是它的外观:

在此处输入图像描述

向下滚动到卡车部分后:

在此处输入图像描述

如果您想要 Brand 和 Model 行之间的其他外观差异,您可以在同一.setData()函数中处理这些差异。

这是一个完整的例子:

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
"""
    
}

以及我与原型单元一起使用的故事板的来源:

<?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>

推荐阅读