티스토리 뷰
애플에서 예제로 보여준 Fruta 앱 처럼 macOS, iOS, and iPadOS에서 하나의 코드베이스로 다 동작하는 앱을 만들 수 있게 되었습니다. 👏
(iOS 14, XCode 12에서부터, SwiftUI를 이용해서)
[1] Multi-platform 프로젝트 만들기
Xcode 12 베타버전을 열어주세요
새 프로젝트 만들기를 누르면 Multiplatform이라는 탭이 생겨있습니다.
거기서 App을 눌러주세요
프로젝트 이름을 입력해주고 프로젝트를 만들면
Shared, iOS, macOS 라는 그룹이 생겨져있습니다.
저는 test도 체크해서 저렇게 Tests iOS, Tests macOS라는 그룹도 생겼어요
그리고 iOS 또는 macOS로 돌려볼 수 있게 되어있어요 (mac OS는 빅서로 올려야지 돌리거나 프리뷰 볼 수 있습니다-!!)
프리뷰로 한번에 다 보면서 개발하고 싶은데,
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView().previewDevice("iPad Air 2")
ContentView().previewDevice("iPhone SE")
ContentView().previewDevice("Mac")
}
}
}
iOS, macOS 바꿔가면서 프리뷰 봐야하는 것이 조금 아쉽네요ㅠㅠ
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
#if os(iOS)
Group {
ContentView().previewDevice("iPad Air 2")
ContentView().previewDevice("iPhone SE")
}
#else
ContentView().previewDevice("Mac")
#endif
}
}
[2] 새로 추가된 App과 Scene
주목할만한 점은 새로 만든 프로젝트에 AppDelegate나 SceneDelegate가 없습니다. 🤭
이제 SwiftUI는 UIKit의 AppDelegate and SceneDelegate에서 벗어나 자신만의 app-lifecycle을 가지게 되었다고 합니다.
그래서 iOS14부터 App이라는 프로토콜을 제공해주고 이걸로 앱의 구조를 정의하라고 합니다.
문서에 multiple platform을 지원하는 single app을 만들고 싶다면, 이 프로토콜을 사용하라고 적혀있네요 :-)
앱을 만들때 App프로토콜을 따르는 App instance 를 만들면 됩니다. (엄청 직관적..!)
@main 으로 앱의 entry point(시작점)을 표현해줍니다.
App 프로토콜은 main() method에 대한 디폴트 구현을 제공해주고 시스템은 앱을 런치할때 저 메소드를 호출해줍니다.
앱의 body는 Scene protocol을 따르는 instance여야합니다. Scene도 iOS14에 새로 나온 친구인데요,
각각의 Scene은 rootView와 시스템에 의해서 관리되는 lifecycle을 가집니다.
이미 Scene프로토콜을 따르게 구현해둔 WindowGroup을 사용해도 되고
@available(iOS 14.0, OSX 11.0, tvOS 14.0, watchOS 7.0, *)
public struct WindowGroup<Content> : Scene where Content : View {
따로 Scene을 만들어도 된다고합니다.
WindowGroup 는 SwifUI View들을 담고 있는 container scene 같은 것이라고 합니다.
import SwiftUI
@main
struct MultiPlatformExampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
Text("Hello, world!").padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
그리고 Scene의 onChange(of:perform:) 와 scenePhase 를 이용해서
Scene의 상태에 대한 변화를 알고 원하는 것을 해줄 수 있어요-! (원래라면 AppDelegate, SceneDelgate 메소드에서 해줄텐데)
@main
struct MultiPlatformExampleApp: App {
@Environment(\.scenePhase) private var scenePhase
var body: some Scene {
WindowGroup {
ContentView()
}.onChange(of: scenePhase) { phase in
switch scenePhase {
case .active:
print("")
case .background:
print("")
case .inactive:
print("")
default:
print("")
}
}
}
}
예를 들어 애플 예제에서 백그라운드 상태일때 cache를 clean한 것 처럼 해줄 수 있겠네요
정리해보면 이렇게 됩니다.
App은 Scene을 가지고
Scene은 View를 가진다.
[3] 화면 사이즈 또는 OS 별로 다른 Navigation 설정하기
아이폰 가로모드, 아이패드, 맥에서는 사이드바를 쓰도록
아이폰 세로모드일때는 탭바를 쓰도록 해볼게요-!
우선 공통뷰 View1, View2를 Shared 그룹에 만들어줍니다.
타켓은 iOS, macOS로 해주세요
그 다음, iOS에서만 쓸 AppTabNavigation을 만들어줍니다. 타켓은 iOS만!
Fruta 앱과 똑같이 만들어줬어요
탭은 두개-!
import SwiftUI
struct AppTabNavigation: View {
@State private var selection: Tab = .menu
var body: some View {
TabView(selection: $selection) {
NavigationView {
View1()
}
.tabItem {
Label("Menu", systemImage: "list.bullet")
.accessibility(label: Text("Menu"))
}
.tag(Tab.menu)
NavigationView {
View2()
}
.tabItem {
Label("Favorites", systemImage: "heart.fill")
.accessibility(label: Text("Favorites"))
}
.tag(Tab.favorites)
}
}
}
// MARK: - Tab
extension AppTabNavigation {
enum Tab {
case menu
case favorites
}
}
// MARK: - Previews
struct AppTabNavigation_Previews: PreviewProvider {
static var previews: some View {
AppTabNavigation()
}
}
그 다음, iOS, macOS에서 모두 쓸 AppSidebarNavigation을 만들어줍니다. (타켓은 iOS, macOS 다 해주고 Shared 그룹에 넣어주세요)
struct AppSidebarNavigation: View {
enum NavigationItem {
case menu
case favorites
}
@State private var selection: Set<NavigationItem> = [.menu]
var sidebar: some View {
List(selection: $selection) {
NavigationLink(destination: View1()) {
Label("Menu", systemImage: "list.bullet")
}
.accessibility(label: Text("Menu"))
.tag(NavigationItem.menu)
NavigationLink(destination: View2()) {
Label("Favorites", systemImage: "heart")
}
.accessibility(label: Text("Favorites"))
.tag(NavigationItem.favorites)
}
.listStyle(SidebarListStyle())
}
var body: some View {
NavigationView {
sidebar
}
}
}
struct AppSidebarNavigation_Previews: PreviewProvider {
static var previews: some View {
AppSidebarNavigation()
}
}
selection에 Set을 써준 이유는
여기에 Set넣어줘야해서,,,!! (좀더 찾아보기)
그리고 ContentView로 가서
EnvironmentValues 중, horizontalSizeClass를 사용해서
iOS (아이폰+아이패드) 라면 가로가 compact일때는 탭으로 나오도록, regular 일때는 사이드바로 나오도록 해주고
Mac 이라면 사이드바로 나오도록 해줍니다.
import SwiftUI
struct ContentView: View {
#if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
#endif
var body: some View {
#if os(iOS)
if horizontalSizeClass == .compact {
AppTabNavigation().accentColor(.green)
} else {
AppSidebarNavigation().accentColor(.green)
}
#else
AppSidebarNavigation().accentColor(.green)
#endif
}
}
그리고 아이패드에서 돌려보면
아이폰에서 돌려보면
맥에서 돌려보면
맥에서는 accentColor가 먹지 않아서 기본 파란 색으로 나왔어요ㅠㅠ
그리고 frame을 설정해줘서 맥앱의 최소사이즈, 최대사이즈를 설정해줄 수 도 있답니다!
Reference
heartbeat.fritz.ai/building-a-multi-platform-app-with-swiftui-5336bce94689
'🍏 > SwiftUI + Combine' 카테고리의 다른 글
[Combine] Future (0) | 2020.09.03 |
---|---|
[SwiftUI] 뷰의 이니셜라이저에서 @State, @Binding, @EnvironmentObject에 접근하기 (1) | 2020.08.31 |
[SwiftUI] ZStack 실전 예제들 (2) | 2020.05.16 |
[Combine] Cancellable과 AnyCancellable (6) | 2019.06.28 |
Rx에서 Combine으로 바꿔본 후기와 느낀점 (2) | 2019.06.28 |
- Total
- Today
- Yesterday
- cocoapod
- Flutter Spacer
- 장고 URL querystring
- flutter dynamic link
- flutter build mode
- Django FCM
- 플러터 싱글톤
- Flutter Text Gradient
- ipad multitasking
- Sketch 누끼
- PencilKit
- DRF APIException
- 장고 Custom Management Command
- Python Type Hint
- Flutter 로딩
- 구글 Geocoding API
- 플러터 얼럿
- Flutter Clipboard
- SerializerMethodField
- Django Heroku Scheduler
- github actions
- Watch App for iOS App vs Watch App
- METAL
- flutter deep link
- drf custom error
- Flutter getter setter
- Django Firebase Cloud Messaging
- ribs
- flutter 앱 출시
- Dart Factory
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |