美文网首页
Swift及SwiftUI学习笔记

Swift及SwiftUI学习笔记

作者: 云上听风 | 来源:发表于2019-11-19 23:00 被阅读0次

    持续更新中......

    swift官方文档


    swift官方文档(英文)

    协议


    swift主要基于协议编程的,所以协议无处不在。
    先看看这篇文章:Swift学习:协议

    官方的一些常用协议:
    1. Equtable
    在Swift中可以通过实现Equatable协议使自定义类型支持==以及!=这两种运算符。

    2. Comparable
    Comparable协议继承于Equatable,实现Comparable协议可以在Equatable的基础上使类型支持>,>=,<,<=四种运算符。

    3. CustomStringConvertible
    当类实现这个协议,可使用print打印类的自定义信息。

    4. CustomDebugStringConvertible
    当类实现这个协议,可使用debugPrint打印类的自定义信息。

    5. Hashable
    继承于Equatable。
    一个类型为了存储在集合中,该类型必须是可哈希化的-该类型必须提供一种方法计算它的哈希值,一个哈希值为Int类型,相等的对象哈希值必须相同。
    Swift的所有基本类型(形如String,Int,Double,Bool)默认是可哈希化的,可以作为集合的值的类型或者字典的键的类型。没有关联值的枚举成员值默认也是可哈希化的。

    用我的话来说:当想把自定义对象当做key存入set和dict中时,那么这个key必须是哈希的,并且要重载等号来对比key之间的不同。

    官方的文档说的比较清楚,这里有篇中文翻译:
    Swift之Hashable协议

    6. Codable
    Codable = Decodable & Encodable
    顾名思义,即编解码序列化。
    具体参看以下文章:
    Swift 4.0: Codable
    Swift 4 踩坑之 Codable 协议

    这里的“&”符号的意思是协议合成:
    协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中。
    协议合成的意思是都罗列的协议都需要遵循。
    其实不仅仅是协议可以合成,类也可以跟协议一起使用“&”符号合成。

    7. CaseIterable
    一句话:CaseIterable被用于合成简单枚举类型的 allCases 静态属性。
    Swift 4.2 新特性详解 CaseIterable.allCases
    Swift--enum枚举,协议CaseIterable

    8. Identifiable
    这个协议只需要你定义一个 id 属性,这个属性必须是一个 Hashable 类型。

    协议和泛型


    加粗的这句话非常重要:

    针对Class和Function,都是通过<Type>来定义。而当我们需要给协议实现一个泛型的时候,需要使用associatedtype关键字来定义泛型:这里需要注意,泛型协议并不能像普通协议那样作为一个类型使用,这是因为 GenericProtocol 表示一组类型,并不是一个单一类型。比如你有一个关于 GenericProtocol 的随机数组,并不能确定每个元素的 magic() 方法返回的类型到底是什么,因为数组中每个元素可能都是不同的。

    泛型最终在使用时都要被推断出来,一种情况是实现的时候指定了类型,另一种在调用时明确类型。

    参考:

    1. swift的泛型协议为什么不用<T>语法
    2. 为什么 Swift 关联类型的协议需要做为泛型约束使用(译)

    元类型.Type 与 .self


    一开始看到官方例子中有一行这样的代码:

    func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    ...
    }
    //调用方法
    let landmarkData: [Landmark] = load("landmarkData.json")
    

    对于as type: T.Type = T.self这一部分不理解,搞懂这个需要先学习元类型。

    元类型就是类型的类型。

    let n : Int = 5
    

    变量n的类型是Int类型,这个很容易理解。
    那么Int的类型又是什么呢?
    Int的类型就是:Int.Type,类型的类型,即元类型。
    说完类型来说值:
    n的类型是Int,值是5。

    let m: Int.Type = Int.self
    

    那么变量m的类型是元类型Int.Type,m的值是Int.self

    到这儿我们再回头来看上面的代码:

    func load<T: Decodable>(_ filename: String, as type: T.Type = T.self) -> T {
    ...
    }
    

    as在这儿是标签名字,type是参数名,它的类型是:T.Type元类型,默认值是T.self
    所以我们也可以这样调用方法,跟不写第2个参数是等价的:

    let landmarkData: [Landmark] = load("landmarkData.json", as: [Landmark].self)
    

    class和static的区别


    class和static用于修饰方法和计算属性使其成为静态方法或者静态属性。

    最大的区别:

    1. class只能在类中使用,static可以在类和结构中使用。
    2. class修饰的方法可以被子类重写,而static不能被重写。

    其他细节的区别参看:
    swift3.0 中class和static

    ForEach和\.self


    官方例子:

    static var previews: some View {
            ForEach(["iPhone SE", "iPhone XS Max"], id: \.self) { deviceName in
                LandmarkList()
                    .previewDevice(PreviewDevice(rawValue: deviceName))
                    .previewDisplayName(deviceName)
            }
            .environmentObject(UserData())
        }
    

    需要先学习这篇文章:
    SwiftUI 和 Swift 5.1 新特性(3) Key Path Member Lookup

    enum: if case,guard case,for case


    模式匹配第四弹:if case,guard case,for case

    关键字


    associatedtype:
    从字面上来理解,就是相关类型。意思也就是被associatedtype关键字修饰的变量,相当于一个占位符,而不能表示具体的类型。具体的类型需要让实现的类来指定。

    typealias:
    用来为已经存在的类型重新定义名字的,通过命名,可以使代码变得更加清晰。使用的语法也很简单,使用typealias 关键字像使用普通的赋值语句一样,可以将某个已经存在的类型赋值为新的名字。

    Combine


    Combine可以看作是简化版的RxSwift,Combine目前学习资料不多,可以先学RxSwift了解基本概念,再使用下面的对照速查表快速学习Combine。
    RxSwift中文文档
    RxSwift 使用详解系列
    Swift Combine 入门导读
    RxSwift到Apple的Combine速查表(Cheat Sheet)

    Apple Combine 的开源实现:
    有空可以看看,对于加深Combine的理解有好处。
    有两个项目:

    1. OpenCombine
      项目地址:broadwaylamb/OpenCombine

    2. Apple Combine 的开源实现 CombineX 的第一个 beta 发布啦!
      项目地址:luoxiu/CombineX

    SwiftUI学习资料


    1. 这个是官方Demo的中文翻译文档
      建议先从这里开始学,结合源码学的更快。
      WillieWangWei/SwiftUI-Tutorials

    2. 基本控件、状态流、手势等知识的简单说明和示范
      Jinxiansen/SwiftUI

    3.SwiftUI之属性包装
    讲解了以下属性包装:

    @State
    ObservableObject
    @Published
    @ObservedObject
    @EnvironmentObject
    @Environment
    @Binding
    @GestureState

    学习SwiftUI遇到的问题和bug


    1. SwiftUI update navigation bar title color
    2. 滚动ScrollView到指定位置:
      SwiftUI ScrollView: How to modify .content.offset aka Paging?
    3. 键盘弹出/隐藏时修改布局
      How to show complete List when keyboard is showing up in SwiftUI
    4. SecureField
    • 只要在页面中使用了SecureField那么该页面的第三方输入法就被禁用,在其他TextField上也不能调出。
      在切换到另外页面中可以调出第三方键盘,不过因为在此页面中已经被切换到系统输入法,所以在别的页面需要手动切换第三方键盘。
    • 运行时焦点切换到SecureField会打印以下log:

    [AutoFill] Cannot show Automatic Strong Passwords for app bundleID: xxx.xxx due to error: Cannot save passwords for this app. Make sure you have set up Associated Domains for your app and AutoFill Passwords is enabled in Settings

    可以忽略不管。
    参看此篇文章:WWDC18 iOS 自动生成强密码和自动填充验证码/密码

    1. 去掉导航头
    .navigationBarHidden(true)
    .navigationBarBackButtonHidden(true)
    .navigationBarTitle("注册", displayMode:.inline)//tmd,必须设置这个才能真使.navigationBarHidden(true)有效
    
    1. xcode的一个bug
      在一个文件中SwiftUI预览时右边工具栏的属性栏始终出不来,另一个文件可以看到属性栏,折腾了半天把文件名一改属性栏居然出来了,再把文件名改回去也一切正常。
    2. 去掉List或者Form的左边空白
      加在List/Form的子级中:
    .listRowInsets(EdgeInsets())
    

    以上还会留一点点空白,使用以下把空白完全去除:

    .listRowInsets(EdgeInsets(top: 0, leading: -8, bottom: 0, trailing: 0))
    

    或者

    .padding(.horizontal, -24)
    

    以上的-8,-24是自己对着界面调出来的,不一定通用(可能在不同平台有不同表现)。一般问题不用太精确,凑合用着可以了。

    1. 不要随便用Button
      在Form的Section底下用了Button,当点击一栏中不被其他控件比如TextField完全填充的空白区域时都会激活Button的action事件,把Button换成Image并且使用onTapGesture事件就正常了。
      Image点击事件代码例子:
     Image(systemName: "xmark.circle.fill")
                                        .foregroundColor(.gray).opacity(0.5)
                                        .imageScale(.small)
                                        .onTapGesture {
                                               print("image click")
                                        }
    
    1. TextField在系统自带的简体拼音输入法时有bug
      首先是输入时键盘上的候选区闪动,另外输入会莫明其妙的被自动删除或者多出字符,总之该输入法在TextField中基本不能用。
      然后我换成UITextField后就没问题了,shit!
      目前iOS13.2.3依然有此bug。

    2. TextField和SecureField回车后无法自动切换到下一个输入框。

    3. 根据上面第9和第10条,TextField和SecureField最好还是别用,用UIKit版封装一下替代。

    4. 最好不要自定义previewDevice

    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ForEach("iPhone SE","iPhone XS Max", "iPhone 11 ", "iPhone 11 Pro"], id: \.self) { deviceName in
                ContentView()
                    .previewDevice(PreviewDevice(rawValue: deviceName))
                    .previewDisplayName(deviceName)
            }
            .environmentObject(UserData())
        }
    }
    
    • 以上"iPhone 11 "在我机器上一定要加后面那个空格,否则preview时一直出不来。
    • 我实际上只使用一个设备比如"iPhone XS Max"或者"iPhone 11 Pro",当使用Xcode一段时间后,Xcode的子进程:SourceKitService会疯狂占用内存,占用的内存多达几十G,机器变得非常慢,最后只好强制杀死SourceKitService进程。
      不使用以上自定义previewDevice,只使用以下默认代码则一切正常:
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    1. 改变全屏颜色包括SafeArea区域
    //  Color实际是个View,所以可以使用edgesIgnoringSafeArea来忽略安全区达到全屏效果
    .background(Color(red: 0.9, green: 0.9, blue:0.9).edgesIgnoringSafeArea(.all))
    
    1. 当在xcode中打开多个Tab窗口时,只在一个Tab中开启Preview。如果在多个Tab中开启Preview的话可能会预览失败。

    Font Icon和SF Symbols


    Font Icon:
    开源库好几个,我先选择了这个:
    ranesr/SwiftIcons
    不过后来发现苹果官方有SF Symbols

    SF Symbols:
    ios13以上系统自带SF Symbols,应该也是Font Icon,可以直接调用。
    官方文档说明:
    SF Symbols
    要想知道所有符号名字需要下载上面页面提供的一个mac软件,不过直接下载很慢,使用迅雷快很多。

    所以使用SwiftUI开发APP的话可以不使用第三方FontIcon直接使用SF Symbols
    调用例子:

     Image(systemName: "arkit").foregroundColor(.blue)
    

    相关文章

      网友评论

          本文标题:Swift及SwiftUI学习笔记

          本文链接:https://www.haomeiwen.com/subject/yakeictx.html