SwiftUI-混合开发

在目前阶段,SwiftUI 很难独立开发一款功能强大的 App,还是需要与 UIKit 一起合作,借助 UIKit 成熟完善的知识体系,二者相互嵌套形成混合开发。

UIKit in SwiftUI

UIKit SwiftUI
UIView UIViewRepresentable
UIViewController UIViewControllerRepresentable

UIViewRepresentable

  • 要使 UIView 在 SwiftUI 中可用,需要用UIViewRepresentable对 UIView 进行包装。
  • UIViewRepresentable中主要有两个方法需要实现:
    • makeUIView:创建View
    • updateUIView:根据条件和业务逻辑设置View的状态。
  • 案例一
import SwiftUI
import UIKit

struct ActivityIndicator: UIViewRepresentable {
    var isAnimating: Bool
    
    func makeUIView(context: Context) -> UIActivityIndicatorView {
        let v = UIActivityIndicatorView()
        v.color = .orange
        return v
    }
    
    func updateUIView(_ uiView: UIActivityIndicatorView, context: Context) {
        if isAnimating {
            uiView.startAnimating()
        } else {
            uiView.stopAnimating()
        }
    }
}


struct ContentView : View {
    
    var isAnimating: Bool = true  
      
    var body: some View {
        ActivityIndicator(isAnimating: isAnimating)
    }
}
  • 案例二
import UIKit
import SwiftUI
import MapKit

struct Map: UIViewRepresentable {
    
    var locationManager:CLLocationManager = CLLocationManager()
    
    func setupManager(){
        
        locationManager.desiredAccuracy = kCLLocationAccuracyBest  
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestAlwaysAuthorization()
    }
    
    func makeUIView(context: Context) -> MKMapView {
        
        self.setupManager() 
       
        let map = MKMapView(frame: UIScreen.main.bounds)
        map.showsUserLocation = true    
        map.userTrackingMode = .followWithHeading 
        return map
        
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        
    }
}

struct ContentView : View {
    
    var body: some View {
        Map()
    }
}
  • 案例三
// 定义一个类负责实现代理
class TextFieldDelegate: NSObject, UITextFieldDelegate {
        
    func textFieldDidBeginEditing(_ textField: UITextField) {
        print("开始编辑")
    }
}

// 定义UIViewRepresentable
struct MyTextField: UIViewRepresentable {
    
    var text: String
    var placeholder: String  
    private let delegate = TextFieldDelegate()
    
    func makeUIView(context: UIViewRepresentableContext<MyTextField>) -> UITextField {
        
        let tmpView = UITextField()
        tmpView.text = text
        tmpView.borderStyle = .roundedRect
        tmpView.placeholder = placeholder
        tmpView.delegate = delegate
        return tmpView
    }
    
    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<MyTextField>) {
    }
}

// SwiftUI使用
struct ContentView : View {
    
    var body: some View {        
        MyTextField(text: "", placeholder: "请输入内容").frame(height: 40).padding()
    }
}
  • 如果要桥接 UIKit 的数据绑定(Delegate,Target/Action),需要使用Coordinator进行协调。
import SwiftUI
import UIKit

// 自定义个SegmentControl控件
struct SegmentControl: UIViewRepresentable {
    
    @Binding var selectedSegmentIndex: Int

    // 下面两个方法都是和 UIKit 相关
    func makeUIView(context: Context) -> UISegmentedControl {
        
        let segmentControl = UISegmentedControl()
        segmentControl.insertSegment(withTitle: "红", at: 0, animated: true)
        segmentControl.insertSegment(withTitle: "黄", at: 1, animated: true)
        segmentControl.insertSegment(withTitle: "蓝", at: 2, animated: true)
        segmentControl.selectedSegmentIndex = selectedSegmentIndex
        
        // 注意这里的参数,与Coordinator有关
        segmentControl.addTarget(
            context.coordinator,
            action: #selector(Coordinator.updateCurrentPage(sender:)),
            for: .valueChanged)

        return segmentControl
    }

    func updateUIView(_ uiView: UISegmentedControl, context: Context) {
        uiView.selectedSegmentIndex = selectedSegmentIndex
    }

    // 协议的方法之一,返回一个协调器
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    // 自定义协调器,UIKit与SwiftUI交互的地方
    class Coordinator: NSObject {
        var control: SegmentControl
        
        init(_ control: SegmentControl) {
            self.control = control
        }
        
        @objc func updateCurrentPage(sender: UISegmentedControl) {
            control.selectedSegmentIndex = sender.selectedSegmentIndex
        }
    }
}

struct ContentView : View {
    
    @State private var selectedSegmentIndex: Int = 1
    
    var body: some View {
        SegmentControl(selectedSegmentIndex: $selectedSegmentIndex)
    }
}

UIViewControllerRepresentable

  • 要使 UIViewController 在 SwiftUI 中可用,需要用UIViewControllerRepresentable对 UIViewController 进行包装。
  • UIViewControllerRepresentable中主要有两个方法需要实现:
    • makeUIViewController:创建UIViewController
    • updateUIViewController:根据条件和业务逻辑设置UIViewController的状态。
  • 案例
import SwiftUI
import UIKit

struct NavigationViewController: UIViewControllerRepresentable {
    
    var vc: UIViewController
    var title: String

    func makeUIViewController(context: Context) -> UINavigationController {
        
        let nvc = UINavigationController(rootViewController: vc)  
        return nvc
    }

    func updateUIViewController(_ navigationController: UINavigationController, context: Context) {
         
        navigationController.viewControllers[0].title = title        
    }
}


struct ContentView : View {
    
    var body: some View {        
        NavigationViewController(vc: UIViewController(), title: "UIViewControllerRepresentable")
    }
}

SwiftUI in UIKit

SwiftUI 中的 View 需要使用UIHostingController包装以后才可以给 UIKit 使用。

UIHostingController

开发 iOS 项目章节已经分析过启动流程,就是通过UIHostingController包装 ContentView,然后赋值给window.rootViewController

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

推荐阅读更多精彩内容

  • 重点参考链接: View Programming Guide for iOS https://developer....
    Kevin_Junbaozi阅读 4,410评论 0 15
  • 由于 API 变动,此文章部分内容已失效,最新完整中文教程及代码请查看 https://github.com/Wi...
    Willie_阅读 7,856评论 29 23
  • 开始之前:请确保你的系统版本为macOS 10.15及以上版本且已经安装了Xcode 11。这种组合使您可以在Xc...
    Augs阅读 2,984评论 0 7
  • 说到与UIKit的集成不免会觉得有些鸡肋,因为现在很难做到只支持iOS13,不过到iOS14时,这种集成就变得必不...
    西西的一天阅读 7,001评论 0 13
  • 有点混乱的一天 昨晚小宝出汗厉害,一晚上换了四件衣服,头发都湿透了。 是热还是虚啊,真担心。 前一晚跟她和哥哥睡床...
    winzy小文子阅读 113评论 0 1