SwiftUI基础篇List

List

概述

文章主要分享SwiftUI Modifier的学习过程,将使用案例的方式进行说明。内容浅显易懂,List部分没有展示部分调试结果展示,不过测试代码是齐全的。如果想要运行结果,可以移步Github下载code -> github案例链接

1、List详细解释

SwiftUI的List视图类似于UITableView,可以根据需求显示静态的列表视图。对比于UITableView使用也更加简单,不需要通过Storyboard或者在代码中register,也不需要告诉它有多少行,也不需要去手动排列和配置cell。

相反,SwiftUI的List是为可组合性而设计的--设计成能够从小的东西构建更大的东西。所以,SwiftUI没有一个大的视图控制器,而是通过构建小的视图进行堆叠。

就代码大小而言,如果不考虑其他因素,差异是惊人的--可以删除几乎所有想象中的UITableView的视图代码,但仍然能得到那种很好的UI和感觉。

Swift 复制代码
List {
    Text("Hello, World!")
}

调试结果

仅3行代码就完成了UITableView的工作。

2、创建static List

要创建项目的静态列表,首先要定义列表中每一行应该是什么样子。这是一个与其他View一样的View,计划在行中显示的数据提供任何参数。一旦有了row,就可以创建一个List视图,该视图可以根据需要创建任意多的行。

Swift 复制代码
struct Pizzeria: View {
    let name: String
    
    var body: some View {
        Text("Restaurant: \(name)")
    }
}

struct FFListStaticitems: View {
    var body: some View {
        //例如,定义一个Pizzeria视图,它将显示一个名称字符串,然后将其用作带有三个固定数据快的Lists的一行。
        List {
            Pizzeria(name: "Joe's Original")
            Pizzeria(name: "The Real Joe's Original")
            Pizzeria(name: "Original Joe's")
        }
    }
}

调试结果

3、创建Dynamic List

为了处理动态项,必须告诉SwiftUI它如何识别哪个item是哪个。这可以通过手动指定标识属性或使用可识别的协议(Identifiable)来完成。协议只有一个要求,即类型必须具有某种类型的id值,SwiftUI可以使用id来标记。

Swift 复制代码
struct Restaurant: Identifiable {
    let id = UUID()
    let name: String
}

struct RestaurantRow: View {
    let restaurant: Restaurant
    
    var body: some View {
        Text("Come and eat at \(restaurant.name)")
    }
}

struct FFListDynamicItems: View {
    let restaurants = [
        Restaurant(name: "Joe's Original"),
        Restaurant(name: "The Real Joe's Original"),
        Restaurant(name: "Original Joe's")
    ]
    
    var body: some View {
        List(restaurants) { restaurant in
            RestaurantRow(restaurant: restaurant)
        }
    }
    //上面大部分的操作都是准备数据,最后一步才是真正的操作,使用List(restaurants)从restaurants数组
    //创建一个列表,对数组中的每个item执行一次闭包,通过RestaurantRow(restaurant: restaurant)创建cell
}

4、删除List中的cell

SwiftUI提供了deleteDisabled()修饰符可以从列表中删除cell。如果只想滑动从数组中删除项,而不添加任何额外的逻辑,那么简单的方法非常有效。使用与列表的数据绑定,并传入editActions参数

4.1、基础侧滑删除

Swift 复制代码
@State private var users = ["Glenn", "Malcolm", "Nicola", "Terri"]

//可以立即滑动来删除行,并且用户数组将更新,如果想移动cell,将.delete 改成.all
List($users, id: \.self, editActions: .delete) { $user in
    Text(user)
}

4.2、deleteDisabled()条件删除

Swift 复制代码
@State private var users = ["Glenn", "Malcolm", "Nicola", "Terri"]

//删除也可以使用deleteDisabled()和任何条件。例如,用户可以随意的删除,但至少满足row > 1
List($users, id: \.self, editActions: .delete) { $user in
    Text(user)
        .deleteDisabled(users.count < 2)
}

4.3、onDelete删除

通常会想要调用Swift的remove(at Offsets:)方法从你的序列中删除行。因为SwiftUI正在监视你的状态,你所做的任何更改都会自动反应在你的UI中。例如,创建一个包含三个项目的ContentView结构体,然后附加一个onDelete(perform:)修饰符,从列表中删除row

Swift 复制代码
@State private var users1 = ["BBlv", "Taylor", "Paul"]

//对于更复杂的删除方法,将onDelete(perform:)修饰符附加到列表中的ForEach,并让它发生在删除操作时调用这个方法,这个处理需要有一个特定的函数来接受多个索引来删除
func delete(at offsets: IndexSet) {
    users1.remove(atOffsets: offsets)
}

List {
    ForEach(users1, id: \.self) { user in
        Text(user)
    }
    .onDelete(perform: { indexSet in
        delete(at: indexSet)
    })
    .navigationTitle("Users")
}

onDelete()作为ForEach的修饰符,不能直接用于List,因为此修饰符是在DynamicViewContent中定义的

5、移动List中的cell

SwiftUI的moveDisabled()修饰符可以移动行,类似于deleteDisabled()修饰符

Swift 复制代码
struct FFListMoveRows: View {
    @State private var users = ["Glenn", "Malcolm", "Nicola", "Terri"]
    @State private var usres1 = ["BBlv", "Taylor", "Paul"]
    var body: some View {
        
        List($users, id: \.self, editActions: .move) { $user in
            Text(user)
        }
        
        //通过moveDisable()修饰符和条件来移动rows
        List($users, id: \.self, editActions: .move) { $user in
            Text(user)
                .moveDisabled(user == "Glenn")
        }
        
        NavigationStack {
            List {
                ForEach(users, id: \.self) { user in
                    Text(user)
                }
                .onMove(perform: { indices, newOffset in
                    move(from: indices, to: newOffset)
                })
            }
            .toolbar {
                EditButton()
            }
        }
    }
    //对于更复杂的move方法,将onMove(preform:)修饰符附加到list中
    func move(from source: IndexSet, to destination: Int) {
        users.move(fromOffsets: source, toOffset: destination)
    }
}

6、List添加Section

创建一个cell备用

Swift 复制代码
struct TaskRow: View {
    var body: some View {
        Text("Task data goes here")
    }
}

6.1、创建附加header的section

Swift 复制代码
List {
    Section {
        TaskRow()
        TaskRow()
        TaskRow()
    } header: {
        Text("Important tasks")
    }
    
    Section {
        TaskRow()
        TaskRow()
        TaskRow()
    } header: {
        Text("Others tasks")
    }
}

6.2、创建附加footer的section

Swift 复制代码
Section {
    Text("Row 1")
    Text("Row 2")
    Text("Row 3")
} footer: {
    Text("End")
}

6.3、headerProminence()修改section的header

默认情况下,section header匹配默认的iOS样式,可以通过headerProminence()修饰符更改

Swift 复制代码
Section {
    TaskRow()
} header: {
    Text("Header")
}
.headerProminence(.increased)

调试结果

7、设置cell的背景颜色

SwiftUI有一个专门的修饰符来设置cell的背景视图,是listRowBackground()。它接受任何类型的视图--包括颜色、图像和形状。

Swift 复制代码
struct SwiftListListRowBackground: View {
    var body: some View {
        List {
            ForEach(0..<10) {
                Text("Row \($0)")
            }
            .listRowBackground(Color.green)
        }
    }
}

调试结果

8、通过listStyle创建List

SwiftUI的列表视图有一个listStyle()修饰符来控制list的样式:

  • grouped
  • insetGrouped
Swift 复制代码
struct ExampleRow: View {
    var body: some View {
        Text("Example Row")
    }
}

struct FFListInsetGroup: View {
    var body: some View {
        //.grouped
        List {
            Section {
                ExampleRow()
                ExampleRow()
                ExampleRow()
            } header: {
                Text("Examples")
            }
        }
        .listStyle(.grouped)
        
        //insetGrouped
        List(0..<100) { i in
            Text("Row \(i)")
        }
        .listStyle(.insetGrouped)
    }
}

9、创建可折叠的List

SwiftUI的List视图有一个增强初始化器,可以创建带有子节点的扩展--以可以点击的箭头的方式呈现,当点击时,箭头转动,显示子节点。要使用这种形式的List,需要有精确的数据,数据模型应该拥有相同类型的可选子节点,这样可以创建树。通常情况下,可能会从JSON或类似的地方加载这类数据。

Swift 复制代码
struct Bookmark: Identifiable {
    let id = UUID()
    let name: String
    let icon: String
    let items: [Bookmark]?
    
    static let apple = Bookmark(name: "Apple", icon: "1.circle", items: nil)
    static let bbc = Bookmark(name: "BBC", icon: "square.and.pencil", items: nil)
    static let swift = Bookmark(name: "Swift", icon: "bolt.fill", items: nil)
    static let twitter = Bookmark(name: "Twitter", icon: "mic", items: nil)
    
    static let example1 = Bookmark(name: "Favorites", icon: "star", items: [Bookmark.swift, Bookmark.apple, Bookmark.bbc, Bookmark.swift, Bookmark.twitter])
    static let example2 = Bookmark(name: "Recent", icon: "timer", items: [Bookmark.apple, Bookmark.bbc, Bookmark.twitter])
    static let example3 = Bookmark(name: "Recommended", icon: "hand.thumbsup", items: [Bookmark.apple, Bookmark.swift, Bookmark.bbc,  Bookmark.twitter])
}

struct FFListExpanding: View {
    let items: [Bookmark] = [.example1, .example2, .example3]
    var body: some View {
        List(items, children: \.items) { row in
            HStack {
                Image(systemName: row.icon)
                Text(row.name)
            }
        }
        
    }
}

调试结果

10、滚动到list中的特定cell

让SwiftUI的List移动来显示特定的行。应该把它嵌入到ScrollViewReader中。这在其代理上提供了一个scrollTo()方法,该方法可以移动到列表中的任意行,只需要提供id和锚点。

10.1、scrollTo

Swift 复制代码
ScrollViewReader(content: { proxy in
    VStack {
        Button("Jump to #50") {
            proxy.scrollTo(50)
        }
        
        List(0..<100, id: \.self) { i in
            Text("Example \(i)")
                .id(i)
        }
    }
})

10.2、anchor

将锚点标记为.top

Swift 复制代码
ScrollViewReader(content: { proxy in
    VStack {
        Button("Jump to #50") {
            proxy.scrollTo(50, anchor: .top)
        }
        
        List(0..<100, id: \.self) { i in
            Text("Example \(i)")
                .id(i)
        }
    }
})

10.3、withAnimation

将锚点标记为.top,以动画效果过渡

Swift 复制代码
ScrollViewReader(content: { proxy in
    VStack {
        Button("Jump to #50") {
            withAnimation {
                proxy.scrollTo(50, anchor: .top)
            }
        }
        
        List(0..<100, id: \.self) { i in
            Text("Example \(i)")
                .id(i)
        }
    }
})

11、List支持多选

SwiftUI的list支持多选,但仅限于编辑模式之下。为了支持多选,首先添加一个与列表中使用的类型相同的可选属性集合。例如,如果一个整数list,那么将有一个可选的Int。使用他的选择参数将它传递给list,确保list处于编辑模式。

Swift 复制代码
struct FFListSelectionRow: View {
    @State private var selection = Set<String>()
    let names = ["BBLv", "Cyril", "Lana", "Mallory", "Sterling"]
    
    var body: some View {
        NavigationStack {
            List(names, id: \.self, selection: $selection) { name in
                Text(name)
            }
            .navigationTitle("List Selection")
            .toolbar {
                EditButton()
            }
        }
    }
}

12、List的分隔符的隐藏和颜色

SwiftUI提供了两个修饰符来控制其列表中行分隔符的外观:

  • listRowSeparator()用于控制行分隔符是否可见,
  • listRowSeparatorTint()用于控制分隔符的颜色
Swift 复制代码
struct FFListSeparator: View {
    var body: some View {
        //隐藏行分隔符
        List {
            ForEach(1..<100) { index in
                Text("Row \(index)")
                    .listRowSeparator(.hidden)
            }
        }
        //设置分隔符颜色
        List {
            ForEach(1..<100) { index in
                Text("Row \(index)")
                    .listRowSeparatorTint(.red)
            }
        }
    }
}

13、List的下拉刷新

SwiftU的refreshable()修饰符可以附加到list上,当发生拖拽且有一段距离时触发。只要的你的代码完成运行,iOS会自动显示一个菊花。

Swift 复制代码
struct NewsItem: Decodable, Identifiable {
    let id: Int
    let title: String
    let strap: String
}

struct FFListPullRefresh: View {
    @State private var news = [
        NewsItem(id: 0, title: "Want the latest news?", strap: "Pull to refresh!")
    ]
    
    var body: some View {
        NavigationStack {
            List(news) { item in
                VStack(alignment: .leading) {
                    Text(item.title)
                        .font(.headline)
                    Text(item.strap)
                        .foregroundStyle(.secondary)
                }
            }
            .refreshable {
                do {
                    let url = URL(string: "https://www.hackingwithswift.com/samples/news-1.json")!
                    let (data, _) = try await URLSession.shared.data(from: url)
                    news = try JSONDecoder().decode([NewsItem].self, from: data)
                } catch {
                    news = []
                }
            }
        }
    }
}

调试结果

由于加载的速度有一点快,中间菊花没截到。

14、Cell添加自定义侧滑Button

SwiftUI的swipeActions()修饰符可以添加一个或多个滑动button到cell,可选的控制它们属于哪一边,以及它们是否应该被触发使用一个完整的滑动。

14.1、swipeActions

button使用了tint进行了作色,如果未使用,默认是灰色的

Swift 复制代码
Text("Pepperoni pizza")
    .swipeActions {
        Button("Order") {
            print("Awesome!")
        }
        .tint(.green)
    }
Text("pepperoni with pineapple")
    .swipeActions {
        Button("Burn") {
            print("Right on")
        }
        .tint(.red)
    }

14.2、allowsFullSwipe

默认情况下,如果滑动足够远,将自动触发第一个滑动动作。如果想要禁用,在创建滑动时将allowsFullSwipe设置为false。

Swift 复制代码
let friends = ["Antoine", "Bas", "Curt", "Dave", "Erica"]

ForEach(friends, id: \.self) { friend in
    Text(friend)
        .swipeActions(allowsFullSwipe:false) {
            Button {
                print("Muting conversation")
            } label: {
                Label("Mute", systemImage: "bell.slash.fill")
            }
            .tint(.indigo)
            
            Button(role: .destructive) {
                print("Deleting conversation")
            } label: {
                Label("Delete", systemImage: "trash.fill")
            }

        }
}

对于真正删除性质的按钮,应该通过role设定,避免通过tint指定红色

14.3、swipeActions(edge: .leading)

如果想要在cell的两端都添加不同的滑动操作,只需要了用两次swipeActions在边缘

Swift 复制代码
@State private var total = 0

Section {
    ForEach(1..<100) { i in
        Text("\(i)")
            .swipeActions(edge: .leading) {
                Button {
                    total += i
                } label: {
                    Label("Add \(i)", systemImage: "plus.circle")
                }
                .tint(.indigo)
            }
            .swipeActions(edge: .trailing) {
                Button {
                    total -= i
                } label: {
                    Label("Subtract \(i)", systemImage: "minus,circle")
                }
            }
    }
} header: {
    Text("Total: \(total)")
}
.headerProminence(.increased)

调试结果

15、List的Binding

SwiftUI可以直接从绑定中创建List或ForEach,然后提供内容闭包,显示的数据中的每个元素都提供单独的绑定

Swift 复制代码
struct UserA: Identifiable {
    let id = UUID()
    let name: String
    var isContacted = false
}

struct FFListForEach: View {
    @State private var users = [
        UserA(name: "Taylor"),
        UserA(name: "Justin"),
        UserA(name: "Adele")
    ]
    var body: some View {
        List($users) { $user in
            HStack {
                Text(user.name)
                Spacer()
                Toggle("User has been contacted", isOn: $user.isContacted)
                    .labelsHidden()
            }
        }
    }
}

以这种方式使用绑定是修改列表的最有效方式,因为他不会在只有一个项更改时导致整个视图重新加载

调试结果

相关推荐
小洋人最happy3 天前
SwiftUI基础组件之HStack、VStack、ZStack详解
swiftui·vstack·zstack·hstack·spacing
coooliang4 天前
【iOS】SwiftUI状态管理
ios·swiftui·swift
小洋人最happy4 天前
SwiftUI基础组件之List详解
list·swiftui·selection·列表组件·ondelete
struggle20256 天前
Ollmao (OH-luh-毛程序包及源码) 是一款原生 SwiftUI 应用程序,它与 Ollama 集成,可在 Mac 上本地运行强大的 AI 模型
ios·swiftui·swift
iOS阿玮1 个月前
“小红书”海外版正式更名“ rednote”,突然爆红的背后带给开发者哪些思考?
ios·app·apple
货拉拉技术1 个月前
货拉拉用户端SwiftUI踩坑之旅
ios·swiftui·swift
ZacJi1 个月前
巧用 allowsHitTesting 自定义 SignInWithAppleButton
ios·swiftui·swift
刘争Stanley2 个月前
SwiftUI 是如何改变 iOS 开发游戏规则的?
ios·swiftui·swift
1024小神2 个月前
在swiftui中使用Alamofire发送请求获取github仓库里的txt文件内容并解析
ios·github·swiftui
大熊猫侯佩2 个月前
SwiftUI 撸码常见错误 2 例漫谈
swiftui·xcode·tag·tabview·preview·coredata·fetchrequest