英文原版

1.预览快捷键

Option-Cmd-P 

2.使用 @State 前一定加private,用来指定这个属性仅用于当前页面

3.常量绑定.constant(value)

示例:

TextField("Example placeholder", text: .constant("Hello"))
// slider不能滑动
Slider(value: .constant(0.5))

注意:像这样的常量绑定仅用于测试和演示目的——您不能在运行时更改它们。

4.展示测试视图

示例:

struct User: Identifiable {
    let id: String
}

struct ContentView: View {
    let users = (1...100).map { number in "User \(number)" }

    var body: some View {
        NavigationView {
            List(users, id: \.self) { user in
                NavigationLink(destination: Text("Detail for \(user)")) {
                    Text(user)
                }
            }
            .navigationTitle("Select a user")
        }
    }
}

5.container容器超过10个view的限制怎么办?

SwiftUI中的所有容器必须返回不超过10个子view,这在大多数情况下是可以接受的。 但是,如果需要有10个以上的视图,如果需要从实体属性返回多个视图,或者如果需要返回几种不同类型的视图,则应使用如下组:

struct ContentView: View {
    var body: some View {
        List {
            Group {
                Text("Row 1")
                Text("Row 2")
                Text("Row 3")
                Text("Row 4")
                Text("Row 5")
                Text("Row 6")
            }

            Group {
                Text("Row 7")
                Text("Row 8")
                Text("Row 9")
                Text("Row 10")
                Text("Row 11")
            }
        }
    }
}

Group是纯粹的逻辑容器——它们不会影响布局。

6.依靠自适应填充padding()

SwiftUI允许让我们精确地控制在视图周围应用多少填充,如下所示:

Text("Row 1")
    .padding(10)

虽然总是像这样控制填充以“让事情变得恰到好处”很有诱惑力,但如果使用padding()修饰符而不使用任何参数,则会得到自适应填充,即自动根据其内容和环境进行调整的填充。

7.合并文本视图Text

在Swift中,字符串可以用+拼接,在SwiftUI中,Text这个类似View的视图也可以拼接

struct ContentView: View {
    var body: some View {
        Text("Colored ")
            .foregroundColor(.red)
        +
        Text("SwifUI ")
            .foregroundColor(.green)
        +
        Text("Text")
            .foregroundColor(.blue)
    }
}

8.依赖隐式VStack容器

struct User: Identifiable {
    let id = UUID()
    let username = "Anonymous"
}

struct ContentView: View {
    let users = [User(), User(), User()]

    var body: some View {
        List(users) { user in
            VStack{ // 默认是VStack
                Image("apple")
                    .resizable()
                    .frame(width: 40, height: 40)
                Text(user.username)
            }
        }
    }
}

如果不写默认是VStack,所以以上代码可以省略VStack容器

List(users) { user in
    Image("apple")
        .resizable()
        .frame(width: 40, height: 40)
    Text(user.username)
}

特别是List中的item是单个视图,经常是省略容器VStack

List(users) { user in
    // 省略了VStack,使用隐藏的容器VStack
    Text(user.username)
}

注意:系统之前是默认HStack,最新已经改为VStack

9.把action事件从View中独立出来

示例:

Button("Show on Map", action: showOnMap)
func showOnMap() {
    showingMap = true
}

参考: https://www.hackingwithswift.com/articles/226/5-steps-to-better-swiftui-views

其实这是个有意思的问题,在Obj-C中,action都是独立出来的,很多人给Button添加Block,让button的action可以写在闭包里,以此实现代码的高聚合.

于是在Swift代码里,action都是控件的closure,现在国外的大佬又建议把action单独出来,写成方法,以此提高代码的阅读性,呵呵~

我认为各有优势,没必要一刀切,全部用closure,或全部用function都是没必要的,当closure里面的代码比较简单,行数不多,那么就用closure,如果closure里面代码量多,逻辑复杂,那就抽出成一个单独的方法.

10.使用extension进行模块化,比如把View Variables抽到一个extension中

struct GameView: View {
    var body: some View {
        ...
    }
}

// MARK: - View Variables
extension GameView {
    // 测试用,非View变量
    private var testStr: String { "hello" }

    private var gradientBackground: some View {
        LinearGradient(
            gradient: Gradient(colors: gradientBackgroundColors),
            startPoint: .top,
            endPoint: .bottom
        )
    }
    
    
    private var optionButtons: some View {
        ForEach(GameItem.allCases, id: \.self) { gameItem in
            GameOptionButton(
                gameItem: gameItem,
                opposingGameItem: self.game.currentItem,
                currentSelection: self.$pendingChoice
            )
        }
    }
}

在SwiftUI中,不仅可以给extension添加计算属性,而且还可以添加存储属性

11.在数组中查询index, 数据中建议加id,这样能保证item的唯一性

struct Task: Equatable, Hashable, Codable, Identifiable {
    let id: UUID
    var title: String
    var isDone: Bool
    
    init(title: String, isDone: Bool) {
        self.id = UUID()
        self.title = title
        self.isDone = isDone
    }
}

// 查询
guard let index = self.userData.tasks.firstIndex(where: { $0.id == self.task.id }) else { return }
guard let index = self.userData.tasks.firstIndex(of: self.task) else { return }

12.使用ForEach遍历数组中指定item

TabView {
    // 遍历0到4
    ForEach(fruits[0...4], id: \.id) { item in
        CardView(data: item)
    }
}

如果数组中有太多元素,只想使用其中几个item,可以使用[0…n],但需要注意数组的item必须大于n+1

13.顶部导航栏右侧编辑按钮,可以使用系统的EditButton

var body: some View {
    NavigationView {
        List {
            // 第一组
            Section { // 无header
                ForEach(order.items) { item in
                    HStack {
                        Text(item.name)
                        Spacer()
                        Text("$\(item.price)")
                    }
                } // 点击删除item
                .onDelete(perform: deleteItems)
            }
            // 第二组
            Section {
                NavigationLink(
                    destination: CheckoutView()) {
                    Text("Place Order")
                }
            }
            .disabled(order.items.isEmpty)
        }
        .navigationTitle("Order")
        .listStyle(.insetGrouped)
        .toolbar {
            EditButton()
        }
    }
}

// 删除item
func deleteItems(at offsets: IndexSet) {
    order.items.remove(atOffsets: offsets)
}