前言

最近需要支援同事開發iOS APP,同事主要用Storyboard開發,自己則想用主流技術SwiftUI開發,因此需要將兩者混合使用,這邊紀錄一下研究過程。

為何想使用SwiftUI,主要使用過Android Jetpack Compose經驗,覺得Jetpack Compose的開發體驗非常好,因此想嘗試SwiftUI。
SwiftUI主要能提供的優點為:

  • 元件佈局異動不用再重新調整約束。
  • ViewController.swift檔名異動時,不用再手動調整Storyboard Scene對應。
  • 實作的局部元件,可容易讓其他頁面重覆使用。
  • 可享受MVVM好處。

經過溝通會以功能模組進行切割,因此只要針對功能模組UI實作、API串接後,後續提供參數給同事進行呼叫即可。

下載原始碼

原始碼下載

專案快速啟動指令

kevin_swiftui_storyboard_mixed_use__xcode [多種開發語言專案管理工具]

驗證項目

  • Storyboard嵌入SwiftUI(這次專案需求)
  • Xib嵌入SwiftUI
  • Storyboard啟動SwiftUI ViewController

待驗證項目

  • SwiftUI 嵌入 Storyboard
  • SwiftUI 嵌入 Xib

驗證過程

專案結構

建立一個Storyboard專案,並在Main.storyboard的Main View Controller Scene新增三個按鈕,分別為Show StoryboardViewControllerShow XibViewControllerShow SwiftUIViewController

這三個按鈕動作分別為:

  • Show StoryboardViewController 啟動 Storyboard View Controller Scene
  • Show XibViewController 啟動 XibViewController.swift
  • Show SwiftUIViewController 啟動 SwiftUIViewController.swift

專案結構

透過UIHostingController元件將SwiftUI View嵌入到ViewController view中。

原始碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//
// MainViewController.swift
// swiftui_storyboard_mixed_use
//
// Created by source on 2022/11/22.
//

import UIKit
import SwiftUI

class MainViewController: UIViewController {

@IBAction func clickShowXibViewController(_ sender: Any) {
XibViewController.show(self)
}

@IBAction func clickShowSwiftUIViewcontroller(_ sender: Any) {
SwiftUIViewController.show(self)
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//
// StorybordViewController.swift
// swiftui_storyboard_mixed_use
//
// Created by source on 2022/11/22.
//

import UIKit
import SwiftUI

class StoryboardViewController: UIViewController {

static func show(_ parent:UIViewController){
parent.show(Self(), sender: parent )
}

override func viewDidLoad() {
super.viewDidLoad()

//將SwiftUI View加入controller view中
let childView = UIHostingController(rootView: SwiftUISubView())
addChild(childView)

childView.view.frame = CGRect(x: 0,
y: UIScreen.main.bounds.height*0.6,
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height*0.3)
self.view .addSubview(childView.view)
childView.didMove(toParent: self)
}

@IBAction func clickClose(_ sender: Any) {
self.dismiss(animated: true,completion: nil)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//
// XibViewController.swift
// swiftui_storyboard_mixed_use
//
// Created by source on 2022/11/22.
//

import UIKit
import SwiftUI

class XibViewController: UIViewController {

static func show(_ parent:UIViewController){
parent.show(Self(), sender: parent )
}

override func viewDidLoad() {
super.viewDidLoad()

//將SwiftUI View加入controller view中
let childView = UIHostingController(rootView: SwiftUISubView())
addChild(childView)
childView.view.frame = CGRect(x: 0,
y:UIScreen.main.bounds.height*0.6,
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height*0.3)
self.view .addSubview(childView.view)
childView.didMove(toParent: self)
}

@IBAction func clickClose(_ sender: Any) {
self.dismiss(animated: true,completion: nil)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
// SwiftUISubView.swift
// swiftui_storyboard_mixed_use
//
// Created by source on 2022/11/22.
//

import SwiftUI

struct SwiftUISubView: View {
var body: some View {
Color.blue
.overlay(
VStack(spacing: 20) {
Text("SwiftUISubView").font(.largeTitle)
})
}
}

struct SwiftUISubView_Previews: PreviewProvider {
static var previews: some View {
SwiftUISubView()
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//
// SwiftUIViewController.swift
// swiftui_storyboard_mixed_use
//
// Created by source on 2022/11/22.
//

import UIKit
import SwiftUI

class SwiftUIViewController: UIViewController {

static func show(_ parent:UIViewController){
parent.show(Self(), sender: parent )
}

override func viewDidLoad() {
//將SwiftUI View加入controller view中
let childView = UIHostingController(rootView: SwiftUIView())
addChild(childView)

//填滿整個畫面
childView.view.frame = CGRect(x: 0,
y: 0,
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height)
self.view .addSubview(childView.view)
childView.didMove(toParent: self)
}


struct SwiftUIView: View {
@Environment(\.presentationMode) var presentationMode
var body: some View {
Color.red
.overlay(
VStack(spacing: 20) {
Text("SwiftUIViewController").font(.largeTitle)

Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Text("Close")
}
})
}
}

struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
}

執行結果

Keyword

1
2
3
驗證, Validation, val-ida-tion
場景, Scene, sc-ene
故事板, Storyboard, story-board