前書き
ACCESS Advent Calender 今年の1日目は@tonionagauzziです。
1月に娘が生まれて、コロナ禍での育児1年生でしたが、会社や周囲の手厚いサポートによって無事に過ごせました。この場を借りて、1年間お世話になった皆さんにお礼を申し上げます。
そしてようやく念願叶い、両家に顔見せに行った帰りの飛行機で、明後日投稿日じゃん!って気づいてスマホで記事書いてます。便利な時代になりましたね!
やったこと
UIKitで書かれた歴史の長いiOSアプリがあります。最近、その Deployment Target が13.0に上がったので、UIViewControllerの一部だけにSwiftUIを導入しました。
また、SwiftUIで、URLは青下線表示で押したらSafariが開くようにしました。
このような Key-Delimiter-Value 形式のViewです。
SwiftUIを導入した狙いは、
- 要素数(縦の行数)が不定なので、SwiftUIの
ForEach
で手軽に可変にしたい - UIKitと比べて20%以下のコード量で同じ内容を記述できるので、開発効率を上げたい
- 単純なViewでデザイン制約がないので、SwiftUI導入のきっかけ作りをしたい
でした。
上に貼ったスクショはプレビューなので、実際は上下に従来のUIKitのパーツが並んでいる想定です。
説明
1. UIViewController上の一部だけをSwiftUIにする
まず、URLは置いといて、UIViewControllerにSwiftUIを埋め込む部分のコードを載せます。
ポイントは3つです。
-
UIHostingController
にSwiftUI Viewを紐付け、UIKitのViewControllerにaddChild
する - SwiftUI Viewを空のUIViewに
addSubView
する - 2の親子が密接するようConstraintを設定する
こう書くと面倒くさそうですがコード量は大したことないです。
struct Item: Hashable {
var name: String
var value: String
}
import SwiftUI
let ITEM_LIST_FOR_PREVIEW = [
Item(name: "名前", value: "tonionagauzzi"),
Item(name: "SNS", value: "https://twitter.com/tonionagauzzi"),
Item(name: "Motto", value: "You can decide you're happy or not.")
]
struct ExtraView: View {
var itemList: [Item] = []
@ViewBuilder
var body: some View {
GeometryReader { geometry in
VStack {
ForEach(itemList, id: \.self) { item in
HStack(alignment: .top) {
Text(item.name)
.frame(maxWidth: geometry.size.width * 0.12, alignment: .leading)
Text(":")
Text(item.value)
.frame(maxWidth: .infinity, alignment: .leading)
}.padding(.bottom, 1)
}
}
.padding(.horizontal, 10)
}
}
}
struct ExtraView_Previews: PreviewProvider {
static var previews: some View {
ExtraView(
itemList: ITEM_LIST_FOR_PREVIEW
)
}
}
import SwiftUI
class MyViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 省略
let extraViewController: UIHostingController<ExtraView> = UIHostingController(
rootView: ExtraView(
itemList: itemList
)
)
addChild(extraViewController)
// extraView は、あらかじめ Xib/Storyboard で空の UIView として Auto Layout 配置しておく
extraView.addSubview(extraViewController.view)
extraViewController.didMove(toParent: self)
extraViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
extraViewController.view.widthAnchor.constraint(
equalTo: extraView.widthAnchor,
multiplier: 1
),
extraViewController.view.heightAnchor.constraint(
equalTo: extraView.heightAnchor,
multiplier: 1
),
extraViewController.view.centerXAnchor.constraint(
equalTo: extraView.centerXAnchor
),
extraViewController.view.centerYAnchor.constraint(
equalTo: extraView.centerYAnchor
)
])
}
)
didMove
は忘れがちですが、処理の終了を通知するもので、しないとviewWillAppear
などライフサイクルに関係するメソッドが呼ばれなくなる可能性があるので、忘れないようにしましょう。
2. URLはClickableにする
そしてURL対応。リンクを検出したら青文字・下線付きにして押せるようにしたいので、ExtraViewを以下のように作り変えました。
struct ExtraView: View {
var itemList: [Item] = []
@ViewBuilder
var body: some View {
GeometryReader { geometry in
VStack {
ForEach(itemList, id: \.self) { item in
HStack(alignment: .top) {
Text(item.name)
.frame(maxWidth: geometry.size.width * 0.12, alignment: .leading)
Text(":")
if item.isUrl {
Button(action: item.actionUrl) {
Text(item.value)
.underline()
.foregroundColor(Color.blue)
}
.buttonStyle(PlainButtonStyle())
.frame(maxWidth: .infinity, alignment: .leading)
} else {
Text(item.value)
.frame(maxWidth: .infinity, alignment: .leading)
}
}.padding(.bottom, 1)
}
}
.padding(.horizontal, 10)
}
}
}
struct ExtraView_Previews: PreviewProvider {
static var previews: some View {
ExtraView(
itemList: ITEM_LIST_FOR_PREVIEW
)
}
}
extension Item {
private var url: URL? {
return URL(string: self.value)
}
fileprivate var isUrl: Bool {
if let url = url {
return UIApplication.shared.canOpenURL(url)
}
return false
}
fileprivate var actionUrl: () -> () {
return {
if let url = url {
UIApplication.shared.open(url)
}
}
}
}
変えたのは、if item.isUrl
で開けるURLかどうかを判定する部分と、必要なExtensionの追加です。
ちなみに.underline()
は1つ目に書かないとValue of type 'some View' has no member 'underline'
というエラーに嵌ってしまいます。
おわりに
そろそろ東京の大地が見えてきたので、書き終わります。
このように、SwiftUIはView1つから簡単に置き換えることが可能なので、大規模プロジェクトでもできるところから導入しましょう!