[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: Equatable>() -> 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..<segments) { index in
  Text("btn\(index)").frame(width: geometry.size.width/CGFloat(segments), height: geometry.size.height, alignment: .center)
}
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 方法的时候也是同一个道理~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 这一节里,我们一起来通过完成一个表单,了解一下SwiftUI中的一些常用控件。其中,涉及的知识点: TextFie...
    西西的一天阅读 4,654评论 0 3
  • 学习新技术书籍就是我们的良师益友,为了方便大家学习SwiftUI我计划将主流的iOS和SwiftUI进行一下汇总方...
    iCloudEnd阅读 1,569评论 0 1
  • 1、iOS代码规范 https://developer.apple.com/library/archive/rel...
    張小明阅读 1,092评论 0 0
  • 原创:有趣知识点摸索型文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔...
    时光啊混蛋_97boy阅读 3,753评论 1 11
  • The shortest path to building great apps on every deviceB...
    海边的1984_阅读 3,102评论 1 5