List

  • List具体解说
  • static List
  • Dynamic List
  • 删去List中的cell
  • 移动List中的cell
  • List增加Section
  • 设置cell的布景色彩
  • 经过listStyle创立List
  • 创立可折叠的List
  • 滚动到list中的特定cell
  • List支撑多选
  • List的分隔符的躲藏和色彩
  • List的下拉改写
  • Cell增加自界说侧滑Button
  • List的Binding

概述

文章首要共享SwiftUI Modifier的学习过程,将运用事例的办法进行阐明。内容浅显易懂,List展现部分调试成果,不过测验代码是齐全的。假如想要运行成果,能够移步Github下载code -> github事例链接

1、List具体解说

SwiftUI的List视图类似于UITableView,能够依据需求显现静态的列表视图。比照于UITableView运用也更加简单,不需求经过Storyboard或许在代码中register,也不需求告知它有多少行,也不需求去手动排列和装备cell。

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

就代码大小而言,假如不考虑其他要素,差异是惊人的–能够删去几乎一切幻想中的UITableView的视图代码,但仍然能得到那种很好的UI和感觉。

List {
    Text("Hello, World!")
}

调试成果

仅3行代码就完结了UITableView的作业。

SwiftUI基础篇List

2、创立static List

要创立项目的静态列表,首先要界说列表中每一行应该是什么样子。这是一个与其他View相同的View,方案在行中显现的数据供给任何参数。一旦有了row,就能够创立一个List视图,该视图能够依据需求创立任意多的行。

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

调试成果

SwiftUI基础篇List

3、创立Dynamic List

为了处理动态项,有必要告知SwiftUI它怎么辨认哪个item是哪个。这能够经过手动指定标识特点或运用可辨认的协议(Identifiable)来完结。协议只需一个要求,即类型有必要具有某种类型的id值,SwiftUI能够运用id来标记。

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、基础侧滑删去

@State private var users = ["Glenn", "Malcolm", "Nicola", "Terri"]
//能够当即滑动来删去行,而且用户数组将更新,假如想移动cell,将.delete 改成.all
List($users, id: \.self, editActions: .delete) { $user in
    Text(user)
}

4.2、deleteDisabled()条件删去

@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

@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()修饰符

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备用

struct TaskRow: View {
    var body: some View {
        Text("Task data goes here")
    }
}

6.1、创立附加header的section

List {
    Section {
        TaskRow()
        TaskRow()
        TaskRow()
    } header: {
        Text("Important tasks")
    }
    Section {
        TaskRow()
        TaskRow()
        TaskRow()
    } header: {
        Text("Others tasks")
    }
}

6.2、创立附加footer的section


Section {
    Text("Row 1")
    Text("Row 2")
    Text("Row 3")
} footer: {
    Text("End")
}

6.3、headerProminence()修正section的header

默许情况下,section header匹配默许的iOS样式,能够经过headerProminence()修饰符更改


Section {
    TaskRow()
} header: {
    Text("Header")
}
.headerProminence(.increased)

调试成果

SwiftUI基础篇List

7、设置cell的布景色彩

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

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

调试成果

SwiftUI基础篇List

8、经过listStyle创立List

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

  • grouped
  • insetGrouped
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或类似的地方加载这类数据。

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

调试成果

SwiftUI基础篇List

10、滚动到list中的特定cell

让SwiftUI的List移动来显现特定的行。应该把它嵌入到ScrollViewReader中。这在其代理上供给了一个scrollTo()办法,该办法能够移动到列表中的任意行,只需求供给id和锚点。

10.1、scrollTo

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

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,以动画作用过渡

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处于修正形式。

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()用于操控分隔符的色彩
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会主动显现一个菊花。

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 = []
                }
            }
        }
    }
}

调试成果

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

SwiftUI基础篇List
SwiftUI基础篇List

14、Cell增加自界说侧滑Button

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

14.1、swipeActions

button运用了tint进行了作色,假如未运用,默许是灰色的

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。

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在边缘

@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)

调试成果

SwiftUI基础篇List
SwiftUI基础篇List
SwiftUI基础篇List

15、List的Binding

SwiftUI能够直接从绑定中创立List或ForEach,然后供给内容闭包,显现的数据中的每个元素都供给独自的绑定

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()
            }
        }
    }
}

以这种办法运用绑定是修正列表的最有用办法,因为他不会在只需一个项更改时导致整个视图从头加载

调试成果

SwiftUI基础篇List
SwiftUI基础篇List