はじめに
今更感はありますが、年末年始で時間ができたので以前からずっと気になっていたSwiftUIを触ってみようと、Appleから提供されているTutorialsをやってみました。(英語能力があれなのでGoogle先生にお世話になりながら...)
学生時代に研究でObjective-Cで一般未公開ながらiPad向けアプリを作成したことがありましたが、Swiftはほとんど初心者だったため、Tutorialをやっているだけでも「Swiftってこんな書き方するんだ...」みたいな発見が多々ありました。
せっかくなのでSwift・SwiftUI含め詰まったことを自分用の備忘録として雑多にメモメモ。
やったやつ
Creating and Combining Views
Building Lists and Navigation
Handling User Input
structとclass
struct ContentView: View {
var body: some View {
Text("Turtle Rock")
.font(.title)
.foregroundColor(.green)
}
}
struct
という文字から最初はC言語の構造体みたいな物なのかなと思いきや、関数も宣言できる。
他の言語同様class
もあり、struct
とclass
の違いとしては以下の通り。
-
struct
は継承ができない -
class
は参照型、struct
は値型
PreviewProvider
Xcodeで作成中のViewをプレビューする内容を設定するやつ(雑。
File > New > File...
でSwift UIView
を選択してファイルを作成すると自動で作成されるstruct
。
previews
の中でViewの表示方法を定義できる。
// 普通にContentViewを表示
struct ContentView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ソースを直すとプレビューに即座に反映される。
プレビューからも部品のプロパティを弄れるし、ソースコード側に反映される。
まぁ、 EclipseとかIDEによっては同様な機能を持っている物はある。
面白いと思ったのはPreviewProvider
での定義方法によって表示をある程度自在にできるところ。
例えば、複数の部品をグループ化して表示する。しかもサイズを設定する。
struct LandmarkRow_Previews: PreviewProvider {
static var previews: some View {
Group {
LandmarkRow(landmark: landmarkData[0])
LandmarkRow(landmark: landmarkData[1])
}
.previewLayout(.fixed(width: 300, height: 70))
}
}
例えば、端末別に表示をする等ができる。
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone SE", "iPhone XS Max", "iPad Pro (12.9-inch)"], id: \.self) { deviceName in
LandmarkList()
.previewDevice(PreviewDevice(rawValue: deviceName))
.previewDisplayName(deviceName)
}
}
}
VStackとHStack
簡単にレイアウトできる。HTMLより楽。
-
VStack{}
- 垂直方向にコンポーネントを並べる -
HStack{}
- 水平方向にコンポーネントを並べる
Stackは入れ子にもできる。
また、コンポーネント間の空白や余白も用意されている。
-
Spacer()
- 空白作る -
Padding()
- 余白作る -
Offset()
- 位置をずらす
VStack(alignment: .leading) {
Text("Turtle Rock")
.font(.title)
HStack(alignment: .top) {
Text("Joshua Tree National Park")
.font(.subheadline)
Spacer()
Text("California")
.font(.subheadline)
}
}
アプリ立ち上げ時の起動画面
アプリ立ち上げ時の起動画面(RootView)はSceneDelegate.swift
のscene
関数で設定する。
window.rootViewController
にRootViewに設定したいViewをセットする。
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
// UIHostingController()のrootViewに作成したViewを設定する。
window.rootViewController
= UIHostingController(rootView: LandmarkList())
self.window = window
window.makeKeyAndVisible()
}
}
実際にアプリを実装する時、起動時に必要な初期処理(ファイル読み込みとか?)などはどこで書くのがベターなのかな?
画面と処理は分けた方が良さそうなので、RootViewに設定したViewの中で初期処理をやるのは何か違う気がする。
チュートリアルを進めたら出てくるのかな。
protocol
Javaとかでいうinterface
的なアレ。
構造体でも使える。
// HashableとCodableとIdentifiableを実装している。
struct Landmark: Hashable, Codable, Identifiable {
// 略
}
ObservableObject
データを監視するためのオブジェクトを作成する。
ObservableObject
プロトコルを実装するclass
を作成する。
変更を他クラスから参照できるよう、公開するプロパティには@Published
属性を付ける。
import SwiftUI
import Combine
final class UserData: ObservableObject {
@Published var showFavoritesOnly = false
@Published var landmarks = landmarkData
}
参照する側ではObservableObjectを@EnvironmentObject
属性を付けて宣言する。
struct LandmarkList: View {
// 監視するやーつ
@EnvironmentObject var userData: UserData
var body: some View {
NavigationView {
List {
Toggle(isOn: $userData.showFavoritesOnly) {
Text("Favorites only")
}
ForEach(userData.landmarks) { landmark in
if !self.userData.showFavoritesOnly || landmark.isFavorite {
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
LandmarkRow(landmark: landmark)
}
}
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
}
場所によって参照先の記述方法が変わっているのはなんじゃろ?
-
$userData.showFavoritesOnly
- ドル記号はバインディングしていることを表している模様。値の変更を監視している。 -
ForEach(userData.landmarks) { landmark in
- 下記でself
を付けてこちらにself
を付けない理由はなんだろ? -
!self.userData.showFavoritesOnly
-self
はJavaとかでいうthis
のイメージ。
続く
Tutorialをやっただけですが、面白かったので多分続きます。