티스토리 뷰

728x90
반응형

애플에서 예제로 보여준 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

 

Building a Multi-platform App with SwiftUI

Write your own HackerNews client for iOS and macOS in a single codebase

heartbeat.fritz.ai

 

medium.com/better-programming/swiftuis-new-app-lifecycle-and-replacements-for-appdelegate-and-scenedelegate-in-ios-14-c9cf4a2367a9

 

SwiftUI’s New App Lifecycle and Replacements for AppDelegate and SceneDelegate in iOS 14

Brand new property wrappers and function builders to reduce boilerplate code

medium.com

반응형
댓글