You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI/UIKit如何实现类Vue Slots与Slot Props的功能?

SwiftUI/UIKit 实现类似前端 Slots 的通用列表组件

不管是SwiftUI还是UIKit,都能实现你要的带过滤、排序逻辑,支持自定义列表项(默认用内置视图)的通用组件,对应前端的Slots和作用域插槽概念。下面分别给出两种框架的实现方案,同时解决你遇到的编译错误和参数冗余问题。

SwiftUI 实现方案

SwiftUI的@ViewBuilder特性天然适合实现类似插槽的功能,结合泛型可以轻松打造通用列表组件。核心思路是给组件的自定义列表项闭包设置默认值,这样用户调用时可以省略该参数,自动使用内置视图;需要自定义时,闭包能接收当前列表项的数据(对应作用域插槽)。

完整代码示例

import SwiftUI

// 通用过滤排序列表组件
struct FilteredSortedList<Item: Identifiable, Content: View>: View {
    let items: [Item]
    let filter: (Item) -> Bool
    let sort: (Item, Item) -> Bool
    @ViewBuilder let itemContent: (Item) -> Content
    
    // 带默认参数的初始化器,省略itemContent时自动用默认视图
    init(
        items: [Item],
        filter: @escaping (Item) -> Bool = { _ in true },
        sort: @escaping (Item, Item) -> Bool = { _, _ in false },
        @ViewBuilder itemContent: @escaping (Item) -> Content = { item in
            DefaultListItemView(item: item)
        }
    ) {
        self.items = items
        self.filter = filter
        self.sort = sort
        self.itemContent = itemContent
    }
    
    // 预处理:过滤+排序后的数据源
    private var processedItems: [Item] {
        items.filter(filter).sorted(by: sort)
    }
    
    var body: some View {
        List(processedItems) { item in
            // 调用自定义闭包或默认视图
            itemContent(item)
        }
    }
}

// 内置默认列表项视图
struct DefaultListItemView<Item: Identifiable>: View {
    let item: Item
    var body: some View {
        Text("\(item.id)")
        // 可扩展:如果Item有通用属性(如title),可以改为Text(item.title),或用Mirror做更灵活的默认展示
    }
}

// 使用示例
struct TestView: View {
    // 测试数据模型
    struct User: Identifiable {
        let id: Int
        let name: String
        let age: Int
    }
    
    let userList = [
        User(id: 1, name: "Alice", age: 25),
        User(id: 2, name: "Bob", age: 30),
        User(id: 3, name: "Charlie", age: 22)
    ]
    
    var body: some View {
        VStack(spacing: 20) {
            // 1. 省略自定义视图,使用默认样式
            FilteredSortedList(items: userList)
                .navigationTitle("默认视图")
            
            // 2. 自定义列表项,同时配置过滤、排序规则
            FilteredSortedList(
                items: userList,
                filter: { $0.age > 23 }, // 只显示年龄>23的用户
                sort: { $0.age < $1.age } // 按年龄升序排列
            ) { user in
                // 作用域插槽:拿到当前user数据,自定义视图
                HStack {
                    Text(user.name)
                        .font(.headline)
                    Spacer()
                    Text("\(user.age)岁")
                        .foregroundColor(.secondary)
                }
                .padding(.vertical, 8)
            }
            .navigationTitle("自定义视图")
        }
    }
}

解决你遇到的编译错误

你之前遇到的Generic parameter 'Content' could not be inferred错误,本质是编译器无法推断泛型Content的类型——因为你没有给自定义视图闭包提供默认值,导致省略该参数时,编译器不知道Content应该对应什么视图类型。

上面的代码通过在初始化器中给itemContent设置默认的@ViewBuilder闭包(返回DefaultListItemView),让编译器能明确推断出Content的类型,从而解决编译错误。同时默认参数也让你可以按需省略过滤、排序、自定义视图等参数,避免代码杂乱。

UIKit 实现方案

UIKit没有@ViewBuilder,但可以通过配置闭包实现类似逻辑:定义一个可选闭包,用于自定义cell的内容,默认值为nil;当闭包存在时调用它自定义cell,否则使用内置默认配置。

完整代码示例

import UIKit

// 通用过滤排序列表控制器
class FilteredSortedListVC<Item: Identifiable>: UIViewController {
    // 自定义cell的配置闭包类型:接收当前item和cell
    typealias CellConfigHandler = (Item, UITableViewCell) -> Void
    
    private let items: [Item]
    private let filter: (Item) -> Bool
    private let sort: (Item, Item) -> Bool
    private let cellConfig: CellConfigHandler?
    
    private let tableView = UITableView()
    // 预处理后的数据源
    private var processedItems: [Item] {
        items.filter(filter).sorted(by: sort)
    }
    
    // 带默认参数的初始化器
    init(
        items: [Item],
        filter: @escaping (Item) -> Bool = { _ in true },
        sort: @escaping (Item, Item) -> Bool = { _, _ in false },
        cellConfig: CellConfigHandler? = nil
    ) {
        self.items = items
        self.filter = filter
        self.sort = sort
        self.cellConfig = cellConfig
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setupTableView()
    }
    
    private func setupTableView() {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")
        tableView.dataSource = self
    }
}

// 实现UITableViewDataSource
extension FilteredSortedListVC: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        processedItems.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultCell", for: indexPath)
        let item = processedItems[indexPath.row]
        
        if let configHandler = cellConfig {
            // 使用自定义配置闭包
            configHandler(item, cell)
        } else {
            // 默认cell配置
            cell.textLabel?.text = "\(item.id)"
            cell.selectionStyle = .none
        }
        
        return cell
    }
}

// 使用示例
class TestVC: UIViewController {
    // 测试数据模型
    struct User: Identifiable {
        let id: Int
        let name: String
        let age: Int
    }
    
    let userList = [
        User(id: 1, name: "Alice", age: 25),
        User(id: 2, name: "Bob", age: 30),
        User(id: 3, name: "Charlie", age: 22)
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        
        // 1. 使用默认cell配置
        let defaultListVC = FilteredSortedListVC(items: userList)
        addChild(defaultListVC)
        defaultListVC.view.frame = CGRect(x: 0, y: 88, width: view.bounds.width, height: view.bounds.height/2 - 44)
        view.addSubview(defaultListVC.view)
        defaultListVC.didMove(toParent: self)
        
        // 2. 自定义cell配置,同时设置过滤、排序
        let customListVC = FilteredSortedListVC(
            items: userList,
            filter: { $0.age > 23 },
            sort: { $0.age < $1.age }
        ) { user, cell in
            // 作用域插槽:拿到user数据,自定义cell内容
            cell.textLabel?.text = user.name
            cell.detailTextLabel?.text = "Age: \(user.age)"
            cell.accessoryType = .disclosureIndicator
            cell.selectionStyle = .gray
        }
        addChild(customListVC)
        customListVC.view.frame = CGRect(x: 0, y: view.bounds.height/2 + 44, width: view.bounds.width, height: view.bounds.height/2 - 44)
        view.addSubview(customListVC.view)
        customListVC.didMove(toParent: self)
    }
}

说明

UIKit的方案通过可选闭包实现了类似插槽的功能,默认闭包为nil时使用内置cell配置;需要自定义时,闭包接收当前列表项的数据和cell实例,让你可以灵活配置cell内容,对应前端的作用域插槽。

内容的提问来源于stack exchange,提问作者Yauheni Platau

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 14:24:52