简介

苹果公司于2019年度 WWDC 全球开发者大会上发布SwiftUI,它是基于Swift建立的声明式框架。该框架可以用于 watchOS、tvOS、macOS、iOS 等平台的应用开发。

它的主要目的是帮助开发者从页面布局中解脱出来,将更多的经历放在应用逻辑层面,而不是页面布局以及页面适配方面。

但是,SwiftUI毕竟时间短,有一些功能还没有完善,甚至有些控件是SwiftUI无法满足的。而且对于一个有着多年UIKit框架的开发经验的开发者来说,对于将UIKit封装成SwiftUI是必要的,因此,这篇文章会以UIKit的封装入手,来进行SwiftUI学习的开篇。

UIViewRepresentable 协议

对于SwiftUI的工程的创建,以及基础的View的搭建,我相信不需要我来介绍了,可自行到苹果官方文档中进行查看

UIViewRepresentable 协议是用于将UIView框架封装成SwiftUI的View的。协议内容包括:

  • associatedtype UIViewType : UIView 要呈现的视图
  • func makeUIView(context: Self.Context) -> Self.UIViewType UIView的初始化方法,相当于init方法
  • func updateUIView(_ uiView: Self.UIViewType, context: Self.Context) 当SwiftUI中有新内容更新到UIView上
  • static func dismantleUIView(_ uiView: Self.UIViewType, coordinator: Self.Coordinator) 移除UIView
  • associatedtype Coordinator = Void 协调UIView
  • func makeCoordinator() -> Self.Coordinator 创建自定义实例,当UIView中有更改时传达给 SwiftUI 界面。
  • typealias Context = UIViewRepresentableContext<Self> Context

从协议中基本可以看出我们在自定义View时所需要调用的方法了。

注意

  • UIViewRepresentable 协议必须使用 struct 类型,如果使用 classenum 类型时则会报错。
  • 如果需要给视图添加方法时,需要自定义Coordinator类,并遵循NSObject协议
  • 并在 makeCoordinator 方法中返回Coordinator类的对象

实战

好了,到目前为止,UIKit的框架封装原理已经完毕。下面自定义UITextView,可以直观的查看UIViewRepresentable协议。

这是一个比较简单的UITextView的封装,由于SwiftUI中目前并没有提供多行文本输入,而在应用中,不可避免的会使用到多行文本输入。

import SwiftUI

struct SimpleTextView: UIViewRepresentable {
	// 指定Context 可不写
    typealias Context = UIViewRepresentableContext<SimpleTextView>
    
    /// 自定义TextView的属性
    @State var placeholder: String = ""
    @Binding var text: String
    @State var onEditingChange: ((Bool) -> Void)? = nil
    
    // 创建UITextView
    func makeUIView(context: Context) -> UITextView {
        let textView = UITextView()
        textView.delegate = context.coordinator
        textView.font = UIFont.init(name: "PingFang SC", size: 14)
        textView.backgroundColor = .clear
        textView.textColor = UIColor(0xabbfcc)
        textView.text = placeholder
        return textView
    }
    
    // 更新UITextView中的值
    func updateUIView(_ textView: UITextView, context: Context) {
        if textView.text != self.placeholder && !context.coordinator.isEditing {
            textView.text = text
        }
    }
    
    // 用于UITextView的扩展,由于需要使用UITextViewDelegate协议方法,因此该方法需实现
    func makeCoordinator() -> Coordinator {
        return Coordinator.init(self.placeholder, text: $text, onEditingChange: self.onEditingChange)
    }
    
    // 用于实现UITextViewDelegate方法
    class Coordinator: NSObject, UITextViewDelegate {
        var text: Binding<String>
        var placeholder: String = ""
        var isEditing = false
        var onEditingChange: ((Bool) -> Void)?
        
        init(_ placeholder: String = "", text: Binding<String>, onEditingChange: ((Bool) -> Void)? = nil) {
            self.placeholder = placeholder
            self.text = text
            self.onEditingChange = onEditingChange
        }
        
        func textViewDidChange(_ textView: UITextView) {
            self.text.wrappedValue = textView.text
        }
        
        func textViewDidBeginEditing(_ textView: UITextView) {
            if textView.text.isEmpty || textView.text == self.placeholder {
                textView.text = ""
                self.text.wrappedValue = ""
            } else {
                self.text.wrappedValue = textView.text
            }
            self.isEditing = true
            self.onEditingChange?(true)
        }
        
        func textViewDidEndEditing(_ textView: UITextView) {
            if textView.text.isEmpty || textView.text == self.placeholder {
                textView.text = self.placeholder
                self.text.wrappedValue = ""
            } else {
                self.text.wrappedValue = textView.text
            }
            self.isEditing = false
            self.onEditingChange?(false)
        }
    }
}

从上面的代码中就可以比较直观的查看该协议了。后续我会慢慢的讲解SwiftUI中的属性定义名称如 @State, @Binding, @Published, @Environment, @ObservedObject等所代表的含义以及相对于Swift中的含义。