想写个小桌面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你的返回值是不是可以被确定类型,如果不可以会报错的哦!
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
/background
和frame
顺序会影响布局哦!
例如我先设置 frame 再设置 background 是这样的:
如果反过来就是这样的:
因为 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
其实会改变对象的类型哦!
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中构造函数分为designed
和convenience
函数,其中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绑定
这个是List
和forEach
的时候都有用到的,虽然我不太明白这个是什么奇怪的语法:
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 方法的时候也是同一个道理~