[iOS] 尝试用SwiftUI写个Mac App

想写个小桌面app来帮助我们简化一些流程,于是选择了SwiftUI,开篇记录问题的文儿叭~~
https://developer.apple.com/tutorials/swiftui/creating-and-combining-views

1. some关键词
var body: some View {
    Text("ying")
        .font(.title)
        .frame(maxWidth: .infinity, maxHeight: .infinity)
}

swiftUI里面的最开始是酱紫的,看到some我第一反应就是kindof,实际上其实也类似,其实是用于protocol自动实例类型推断

这里为啥View可以some呢?因为:

public protocol View {
    associatedtype Body : View
    @ViewBuilder var body: Self.Body { get }
}

这篇文章写得很好:https://juejin.cn/post/6844903862290104327

就是当你如果这么写会编译不过,因为实例类型不确定不能比较:

func makeInt() -> Equatable {
    return 5
}

let intA = makeInt()
let intB = makeInt()

if intA == intB {
    print("equal")
}

于是你就需要加个泛型,确保两个返回值都是一个类型:

func makeInt() -> T {
    return 5 as! T
}

let intA: Int = makeInt()
let intB: Int = makeInt()

if intA == intB {
    print("equal")
}

这还是挺麻烦的,所以苹果简化了这个过程,让你可以直接用一个关键词来实现:

func makeInt() -> some Equatable {
    return 5
}

let intA = makeInt()
let intB = makeInt()

if intA == intB {
    print("equal")
}

并且编译器还会给你check你的返回值是不是可以被确定类型,如果不可以会报错的哦!


image.png
2. 布局

同推荐个掘金文:https://juejin.cn/post/6856276793817563144

默认布局都是居中的,可以通过改父容器的对齐方式,或者自身的对齐方式来修改:

var body: some View {
    Text("hhhh")
        .font(.title)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
        .offset(x: 0, y: 10.0)
}

上面的alignment其实就是自身在父view里面的位置偏好,然后offset就是在布局以后再移动一下~

如果是stack可以规定它的子view们如何对齐,比如酱紫:

ZStack(alignment: .topLeading) {
    ...

    VStack {
        ...
    }
}

如果不规定优先级,在stack里面的view默认是平分的:


平分

让我们加个layoutPriority康康:

优先级

.frame起的作用就是提供一个建议的size。还有个很神奇的就是border/backgroundframe顺序会影响布局哦!

例如我先设置 frame 再设置 background 是这样的:


image

如果反过来就是这样的:


image

因为 SwiftUI 里面的view是很廉价的,所以其实border/background都是给当前的view包了一层,然后当先设置background,相当于加了一个子图层为stack的父view。

在本例中,frame为background提供了一个(500, 500)的size,background还需要去问它的child,也就是stack, stack返回了一个自身需要的size,于是background也返回了stack的实际尺寸,这就造成了绿色背景跟stack同样大小的效果。

3. class和struct的区别

这个问题感觉只要是C语言都会问哈哈哈,swift竟然也不可避免。

https://zhuanlan.zhihu.com/p/153377577

Struct支持许多与Class相同的行为,包括方法 (methods) 和初始化器 (initializers),不过它们之间还是有很多不同的,最重要的区别之一是它们的类型 (type) 不同,struct 实例是通过复制传递的,即值类型 (value type);而 class 实例是通过引用传递的,即引用类型(reference type)

值类型
引用类型
4. @State

SwiftUI 有个关键字是@State,知道它是为我下面的报错...

抱错

view持有了一个struct值,然后想修改这个值的时候报错Cannot assign to property: 'self' is immutable,于是就查到了这篇:https://www.jianshu.com/p/854b8f7a604f

@State就是一个标签,贴之前视图是不可以修改这个值。加了之后,只要你修改这变量,界面就会跟着同步修改。这个是现代界面语言都是支持的特性。

也就是说,如果你改了这个属性,那么使用这个属性的UI部分会自动更新哦!

… if we added the special @State attribute before [properties], SwiftUI will automatically watch for changes and update any parts of our views that use that state.

但不要随便用@State哦,我遇到因为加了这个修饰导致数组加不进去元素的状况,因为@State其实会改变对象的类型哦!

截屏2021-07-27 下午10.16.51.png

https://www.it1352.com/2138563.html

@State只能在view body里面修改... 如果你在body以外的地方修改,可以触发 view 刷新,但是其实这个变量的数据没有变,所以适合用于标志位,但是绘制的时候的数据最好是正常的var来获取的,或者计算的

5. 预览

SwiftUI 可以实时预览,但是是需要加这个的哦,不是自动哒:

struct SideBar_Previews: PreviewProvider {
    static var previews: some View {
        SideBar()
            .background(Color.white)
    }
}
6. 初始化

这又是一个报错引发的:


报错

参考:https://www.jianshu.com/p/b14d2430689e

swift中构造函数分为designedconvenience函数,其中convenience函数必须表用类自身的构造函数,通常是init(......),也就是说convenience是对构造函数的进一步扩展。

每一个convenience最终的调用都是designed函数,每一个designed调用的父类必须是一个designed

init(style: UITableViewStyle) {
  super.init(nibName:nil,bundle:nil)
       // Custom initialization
       data = ["1","2","3","4","5"]
    }
    convenience init(style: UITableViewStyle, data:NSArray ) {
       self.init(style: style) //designed函数必须最先调用
       self.data = data
    }

使用convenience修饰的构造函数叫做便利构造函数,通常用在对系统的类进行构造函数的扩充时使用,他的特点:

  • 便利构造函数通常都是写在extension里面
  • 便利函数init前面需要加载convenience
  • 在便利构造函数中需要明确的调用self.init()

那么为啥会有报错呢?因为对于struct是不用convenience表示的哦!

Initializers can call other initializers to perform part of an instance’s initialization. This process, known as initializer delegation, avoids duplicating code across multiple initializers.

7. SwiftUI 给 view 加 delegate

在初始化的时候把delegate传入~ 例如:https://gist.github.com/takoikatakotako/c9d351da76b084b1cc98f39dbfdeaf14

8. List的奇奇怪怪的背景色

MacOS里面的List使用的是ScrollView,然后不知道为啥就是有一层背景色去不掉,于是我search了一下竟然让我换成VStack:

https://onmyway133.com/posts/how-to-change-background-color-in-list-in-swiftui-for-macos/

VStack {
  ForEach(barItemTitle, id: \.id) { barItem in
    SideBarRow(item: barItem)
  }
}

搜到的其他方式比如改row的背景色啥的都没用,如果有胖友知道更好的方式欢迎comment。

9. view的ID绑定

这个是ListforEach的时候都有用到的,虽然我不太明白这个是什么奇怪的语法:

ForEach(barItemTitle, id: \.id)

https://www.hackingwithswift.com/books/ios-swiftui/why-does-self-work-for-foreach

其实好像id就是用于可以识别每行的,这个如果用\.id其实就是barItemTitle里面的item的id作为标识符,所以其实你也可以写成\.title,如果item有title属性。\代替的其实就是item,有些会用\.self来替代,这个时候其实就是比如数组里面每个对象自己的hash值作为标识符哦。

10. Spacer()不可点击

参考:https://blog.csdn.net/madaxin/article/details/117877560

这里其实就是加了一个神奇的contentShape以后就可以整行点击啦~

struct FunctionRow: View {
    @State var item: FunctionItem

    var body: some View {
        HStack {
            Text("\(item.name)")
                .frame(alignment: .leading)
                .foregroundColor(.black)
                .padding()
            Spacer()
            if item.selected {
                Image("icon_checkbox")
                    .frame(width: 50, height: 50)
            }
        }
        .frame(height: 50, alignment: .leading)
        .background(Color.clear)
        .contentShape(Rectangle())
        .onTapGesture {
            item.selected = !item.selected
        }
    }
}
11. 圆角边框怎么做

https://blog.csdn.net/QL_ProCareer/article/details/106055105

直接用.border.cornerRadius是不OK的哦,需要改成用overlay~

.overlay(
  RoundedRectangle(cornerRadius: 10, style: .continuous)
  .stroke(Color.init(r: 63, g: 21, b: 183, a: 1), lineWidth: 1)
)
12. json序列化

建议参考这两个~
https://www.jianshu.com/p/1f194f09599a
https://www.jianshu.com/p/82b41e01248a

13. view绘制的时候不能用 for index 只能用 foreach

很神奇swiftUI非常不喜欢for in或者是index循环(会报错for loop Closure containing control flow statement cannot be used with function builder 'ViewBuilder'),但是竟然可以用 for each,but这不是同一个东西么本质上...

ForEach(0..
14. 如何给自定义的view增加流方法

我们写view的时候都是可以直接用下面这种方式去定义它的颜色之类的:

Text("CKTool")
                .font(.title)
                .foregroundColor(Color.init(r: 140, g: 50, b: 183))
                .frame(alignment: .top)
                .padding()

那么如果我自定义一个view,也想通过这样的方式去改一些属性要怎么办呢?其实很简单,就是加一个方法并且返回值还是当前的view:

struct ChildView: View {
    var theText = ""
    
    @State private var color = Color(.purple)
    
    var body: some View {
        HStack {
            if theText.isEmpty {          // If there's no theText, a Circle is created
                Circle()
                    .foregroundColor(color)
                    .frame(width: 100, height: 100)
            } else {                      // If theText is provided, a Text is created
                Text(theText)
                    .padding()
                    .background(RoundedRectangle(cornerRadius: 25.0)
                                    .foregroundColor(color))
                    .foregroundColor(.white)
            }
        }
    }
    
    // simply modify self, as self is just a value
    public func someModifierOrTheLike(color: Color) -> some View {
        var view = self
        view._color = State(initialValue: color)
        return view.id(UUID())
    }
}

现在就可以酱紫用了ChildView(theText: "Hello world!").someModifierOrTheLike(color: Color.green)

15. optional 的闭包调用

如果一个对象有个属性是 closure,但是这个 closure 可能是空,那么可以酱紫调用:

var tapBlock : ((_ selectedIndex : Int) -> Void)?

self.tapBlock?(index)

其实调用delegate的 optional 方法的时候也是同一个道理~

你可能感兴趣的:([iOS] 尝试用SwiftUI写个Mac App)