4. 实现页面导航与数据传递

任务说明

本案例实现一个两页面的iOS导航流程:首页(列表页)展示一组商品名称,点击任意商品后通过导航控制器跳转到详情页,并将该商品的名称与价格传递给详情页进行展示。通过本案例掌握UINavigationController的栈管理、prepare(for:sender:)正向传值以及可选类型的安全解包。

实现方案

采用Storyboard + Segue搭建界面,使用UITableViewController作为首页展示数据源,详情页使用UIViewController承载两个UILabel。在首页的prepare(for:sender:)方法中根据indexPath取出对应模型,赋值给详情页的公开属性。导航控制器由Storyboard的Navigation Controller入口自动创建,无需额外代码。

代码实现

创建两个Swift文件,分别对应首页控制器与详情页控制器。模型使用轻量级struct,数据在首页控制器内部硬编码。

// Product.swift(模型定义)
import Foundation

struct Product {
    let name: String
    let price: Double
}
// ProductListViewController.swift(首页/列表页)
import UIKit

class ProductListViewController: UITableViewController {
    
    private let products: [Product] = [
        Product(name: "机械键盘", price: 399.0),
        Product(name: "无线鼠标", price: 129.0),
        Product(name: "27寸显示器", price: 1899.0),
        Product(name: "USB-C扩展坞", price: 259.0)
    ]
    
    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return products.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell", for: indexPath)
        let product = products[indexPath.row]
        cell.textLabel?.text = product.name
        cell.detailTextLabel?.text = String(format: "¥%.0f", product.price)
        return cell
    }
    
    // MARK: - Navigation(正向传值)
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard segue.identifier == "ShowProductDetail",
              let indexPath = tableView.indexPathForSelectedRow,
              let detailVC = segue.destination as? ProductDetailViewController else {
            return
        }
        let selectedProduct = products[indexPath.row]
        detailVC.product = selectedProduct
    }
}
// ProductDetailViewController.swift(详情页)
import UIKit

class ProductDetailViewController: UIViewController {
    
    // 由首页在跳转前赋值
    var product: Product?
    
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    private func configureUI() {
        guard let product = product else {
            nameLabel.text = "未知商品"
            priceLabel.text = "--"
            return
        }
        nameLabel.text = product.name
        priceLabel.text = String(format: "¥%.2f", product.price)
    }
}

Storyboard连线配置

在Main.storyboard中完成以下设置:

  • 将Navigation Controller设为Is Initial View Controller,其rootViewController指向ProductListViewController(UITableViewController子类)。
  • 列表页的UITableViewCell设置Style: Subtitle,Identifier: ProductCell,Accessory: DisclosureIndicator。
  • 从列表页的TableView Cell(而非控制器)按住Control拖线到详情页ProductDetailViewController,选择Show类型的Segue,设置Identifier: ShowProductDetail。
  • 详情页中放置两个UILabel,分别连接到nameLabel与priceLabel。

验证方法

使用Xcode 14及以上版本,选择iPhone模拟器(如iPhone 14)运行项目。预期行为:

  1. 首页表格显示4行商品,每行左侧为名称、右侧为价格(整数格式)。
  2. 点击“机械键盘”行,页面推入详情页,导航栏出现返回按钮,详情页显示“机械键盘”与“¥399.00”。
  3. 点击导航栏返回按钮,回到列表页,列表滚动位置保持不变。

判定标准:若点击任意行均能正确显示对应商品的名称与两位小数价格,且返回后列表无异常,则导航与传值功能通过。

导航与数据传递时序 UINavigationController ProductListViewController ProductDetailViewController push prepare(for:sender:) 正向传值内容 1. 获取选中行的 indexPath 2. 从 products 数组取出 Product 模型 3. 赋值给 detailVC.product(可选类型) viewDidLoad guard let product nameLabel.text = product.name priceLabel.text = "¥\(price)" pop(返回按钮) 图例 push/pop 数据传递