🐦 Flutter

Flutter NavigationController 에서 iOS 로 이동(push)해보기

ji-hyun 2024. 1. 13. 14:24

Flutter 로 개발하면서 어려웠던 점은 Native SDK 연동을 해야했던 부분인데

보통 SDK 연동 문서를 보면 안드로이드, iOS 연동 방법만 적혀 있고 React native 나 Flutter 개발자를 위한 연동 방법은 적혀있지 않다.

(따라서 일단 SDK 를 호출하는 부분까지는 업체의 문서를 보며 네이티브 코드를 작성해야 하고, Flutter 에서는 MethodChannel 메서드를 써서 이를 연결시켜줘야 한다.)

 

 

 

그래서 문서를 보고 연동하려면 네이티브 지식을 어느 정도 갖고 있으면 수월한 편인데

6개월 전에 나는 iOS 공부를 하지 않았던 상태였다. 

 

 

 

어느 날, 회사 업무에서 다른 업체의 iOS SDK 를 연동하며 아래 메시지를 받았고 이게 무슨 뜻인지 이해하지 못했다.

 

*****View.GetInstance().Initialize 같은 경우
*****View.GetInstance().StartVidoeAd(parentUIView: self)를 호출하기전에
한번만 호출해주면 되는 소스라서 AppDelegate에서 호출해주셔도 괜찮을 것 같지만

*****View.GetInstance().StartVidoeAd(parentUIView: self)같은 경우에는
상기 코드를 호출해주실때 parentUIView가 ViewController를 전달해주셔야 
해당 ViewController에 addSubView로 저희 화면을 덮어 사용하는 구조로 되어있어

ViewController.swift를 생성하여 거기서 호출해주시는 것을 추천드립니다.

제가 가지고 있는 동작 소스에서는 ViewController.swift의 viewDidLoad에서
Initialize와 StartVidoeAd를 순서대로 호출하고 있습니다.

 

 

여기서 알아 듣는게 하나도 없었다. (ViewController 도 당연히 몰랐다..)

결국 나는 업체에서 추천해준 방법을 구현하지 못하고 AppDelegate.swift 에서 간단하게 호출하는 방식으로 구현해두었다.

 

 

 

ViewController 를 만들어서 호출해야 하는데 위 게임 SDK 를 Modal 형태로 띄움

 

 

 

 

 

나는 위 업무 후 iOS 공부를 시작하게 되었던 것 같다. 

한동안 시간이 흘러.. 위의 업무를 잊고 있었을 때쯤 다른 업체의 SDK 를 연동하게 되는 기회가 찾아왔다.

 

 

 

 

 

6개월 전에 나는 Flutter 화면에서 어떻게 iOS 화면을 이동시키는지에 대해 어려웠는데 찾아본 결과, 이에 대한 자료는 아래 하나 정도밖에 없었다. (아래 자료였던 것으로 기억함)

https://medium.com/@cosinus84/flutter-view-controller-used-in-ios-app-using-coordinator-pattern-8896339d64ba

이제 iOS 공부도 조금했고 SDK 를 본격적으로 연동해보려고 위를 다시 공부해보았는데 위 방법은 Coordinator 패턴을 쓰는 방법이다.

 

 

 

Coordinator 패턴을 쓰면 너무 좋겠지만!

나는 지금 봐도 위 개념은 조금 어렵다고 생각해서 (= 주관적임)

일단 이 글에서는 Coordinator 패턴을 쓰지 않고 간단하게 이동하는 방법에 대해서 설명하겠다. 

(다음 블로그 글에서 Coordinator 패턴을 소개해보겠다! 많관부..?)

 

 

 

 

 

 

 

ViewController

우선 들어가기에 앞서 정말 간단하게 ViewController 개념부터 설명해보려 하는데 (= 참고로 내 뇌피셜에 근거함)

일단 iOS 에서 뷰를 생성하면 자동으로 UIViewController 가 생성이 된다. 

 

해당 예제는 MyViewController 를 생성하여 지정해보았다

 

(위 iOS 문법에서 : 옆에 클래스가 붙은 것은 대개 확장을 한다는 의미이다)

 

이때 뷰에다가 UIViewController 말고 내가 직접 생성한 (UIViewController 를 확장하였음) MyViewController 뷰컨트롤러를 따로 지정하면

MyViewController 에서 해당 뷰의 생명주기를 관리할 수도 있고 뷰 조작(크기 조정이라든가, 버튼 액션 눌렀을 때 동작 정의라든가..) 도 할 수 있다!

 

굉장히 기본적이고 핵심적인 개념이니 자세히 알아두면 좋다!

 

 

 

 

 

 

 

 

Flutter 에서 iOS 뷰로 이동하기

ViewController 개념을 알아보았으니

그럼 이제 Flutter ViewController 에서 iOS ViewController 로 이동하는 방법에 대해서 다뤄볼 것이다.

 

 

우선 Flutter 프로젝트를 생성해주면 기본적으로 생성이 되어 있는 AppDelegate.swift 파일을 열어준다.

거기에 아래와 같이 작성해준다.

 

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private var navigationController: UINavigationController? // NavigationController 선언!
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    // FlutterViewController 를 선언!
    let flutterViewController: FlutterViewController = window?.rootViewController as! FlutterViewController

 

 

일단 NavigationController 를 선언해주고

FlutterViewController 를 변수로 따로 선언해줘야 하는데 이는 window 의 rootViewController 이다!

 

 

나는 여기서 궁금했다.

FlutterViewController 를 설정할 때 window 는 선언되지 않았음에도 어떻게 window 변수를 사용할 수 있었던 걸까?

-> 이 변수를 클릭해보면 FlutterAppDelegate 로 이동된다.

 

 

그렇다..

AppDelegate에서 window 를 직접 선언하지 않고도 사용할 수 있는 이유는 FlutterAppDelegate 클래스가 이를 이미 처리하고 있기 때문이다. Flutter를 사용하는 iOS 애플리케이션에서, FlutterAppDelegate는 AppDelegate의 역할을 확장하여 Flutter와 관련된 추가 기능을 제공한다.

 

 

반복해서 나온 문법이다.

iOS 에서 class 옆에 : 붙는 것은 대개 Flutter 로 따지면 extends 와 같은 것으로 확장의 의미로 쓰인다. 

 

 

 

 

 

 


FlutterAppDelegate와 window 정리

다시 돌아와서 이 둘을 살펴보겠다!

 

1. 자동 window 생성: FlutterAppDelegate는 기본적으로 window 프로퍼티를 포함한다. 이는 일반적인 AppDelegate에서 수동으로 해야 하는 window의 생성 및 초기화 과정을 대신 처리해준다.

 


2. Flutter 환경 설정: FlutterAppDelegate는 Flutter 엔진과 뷰 컨트롤러를 초기화하고, 이를 window의 rootViewController로 설정하는 과정을 자동으로 처리한다. 이는 Flutter 애플리케이션의 뷰와 네이티브 iOS 코드 간 통합한다.

 

 

3. 기본 iOS 생명주기 관리: Flutter 앱에서도 iOS의 기본적인 생명주기 관리 기능이 필요하다. FlutterAppDelegate는 이러한 기능을 제공하면서, Flutter와 관련된 추가적인 처리를 함께 수행한다.

 

 

 

 

 

 

그래서 알겠는데.. window 는 뭐죠..

 

 

 

 

 

 

UiWIndow 개념

window 는 뭘까?

일단 window 는 UiWindow 오브젝트를 가리킨다.

 

The backdrop for your app’s user interface and the object that dispatches events to your views.

 

 

Uiwindow 에 대해서 Apple 공식문서를 보면 위와 같이 써져 있다.

한국어로 쉽게 설명하면 "앱의 사용자 인터페이스를 위한 배경이고 뷰로 이벤트를 전달하는 객체.." 이다.

 

 

위의 설명만 보고 이해하기가 쉽지 않은데 아래 그림을 보고 이해되었다!

 

 

 

UiWindow 는 위의 그림처럼 제일 아래에 위치해 있다. (그래서 backdrop 이라고 표현한 것 같다)

 

찾아보니 window 는 뷰들을 담는 컨테이너이며 이벤트를 전달해주는 매개체 이다.

또한 window 자체는 비어있는 컨테이너이며 뷰를 한 개 이상 포함한다. 그리고 새로운 콘텐츠를 보여주기 위해서 윈도우를 교체하는 대신 뷰를 교체한다.

 

(-> window 자체는 시각적인 표현을 포함하지 않는다!)

 

 

 

 

 

 

 

UiWIndow 역할

그럼 UiWindow 가 갖는 역할은 정확히 무엇일까?

(ChatGPT 가 자세히 설명해주어서 이를 차용해왔다.)

 


1. 컨텐츠의 표시: UIWindow는 애플리케이션의 모든 뷰(View)와 뷰 컨트롤러(View Controller)가 표시되는 공간을 제공한다. 즉, 사용자에게 보여지는 모든 인터페이스 요소는 UIWindow 위에 배치된다.

2. 이벤트 처리: UIWindow는 사용자의 터치와 같은 이벤트를 받아들이고, 이를 적절한 뷰 또는 뷰 컨트롤러로 전달하는 역할을 한다. 이를 통해 사용자와 애플리케이션 간의 상호작용이 가능해진다.

3. 뷰 계층 관리: 애플리케이션의 뷰 계층구조의 루트(root) 역할을 한다. 모든 뷰 컨트롤러와 뷰는 UIWindow 아래에 위치하며, UIWindow를 통해 관리된다.

4. 상태와 방향 관리: UIWindow 는 애플리케이션의 상태(예: 백그라운드, 포어그라운드)와 디바이스의 방향 변경에 대한 대응을 관리한다. 이를 통해 디바이스의 방향이 바뀔 때 적절한 레이아웃 조정이 이루어진다.

5. 다중 창 지원: iPad와 같은 디바이스에서는 여러 UIWindow 를 사용하여 다중 창(multi-window) 환경을 지원할 수 있다. 이를 통해 사용자는 더 효율적인 멀티태스킹 경험을 할 수 있다.

6. 애플리케이션과 환경의 연결: UIWindow 는 애플리케이션과 iOS 운영 체제 간의 중요한 연결점 역할을 한다. 시스템의 다양한 상태 변화나 알림 등을 애플리케이션에 전달하는 데 중요한 역할을 한다.

 

이로써 UIWindow 는 단순히 화면에 무언가를 표시하는 것 이상의 의미를 가지고, 애플리케이션의 사용자 인터페이스 관리 및 이벤트 처리의 핵심적인 부분인 것이다!

 

 

 

 

 

 

 

다시 AppDelegate 로 돌아오자.

 

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private var navigationController: UINavigationController? // NavigationController 선언!
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    // FlutterViewController 를 선언!
    let flutterViewController: FlutterViewController = window?.rootViewController as! FlutterViewController

    navigationController = UINavigationController(rootViewController: flutterViewController)
    navigationController?.isNavigationBarHidden = true
    window?.rootViewController = navigationController
    window?.makeKeyAndVisible()

 

 

여태까지 설명했던 FlutterViewController.. 근데 이걸 왜 가져와야 하는걸까?

이제 다시 또 UINavigationController 를 알아봐야 하는데 사실 이건 이름에서 알 수 있듯 iOS 에서 네비게이션을 구현할 때 방법들 중 하나의 방법이다.

UINavigationController 는 뷰 컨트롤러들을 스택으로 관리하고 사용자가 앱 내에서 이동할 때마다 새로운 뷰 컨트롤러를 스택에 푸시하거나 스택에서 팝하여 화면 전환을 관리해야 한다.

특히 rootViewController 는 네비게이션의 시작점이라고 볼 수 있다!

 

 

그래서 뷰 컨트롤러 간 네비게이션을 하기 위해선 먼저 현재 FlutterViewController 를 rootViewController 로 설정! 하고 iOS 뷰 컨트롤러로 push 해서 이동해야 한다.

그리고 나중에 돌아올 땐 iOS ViewController 에서 self.navigationController?.popToRootViewController(animated: true) 로 FlutterViewController 로 돌아올 수 있다.

 

 

 

 

 

 

 

그럼 AppDelegate 설명은 끝났다. 

그동안 FlutterAppDelegate 님께서 FlutterViewController 를 쉽게 만들어줬지만 이동해야 할 iOS ViewController 는 어떻게 생성하면 될까?

나는 스토리보드로 작업하는 방식이 그나마 익숙해서 스토리보드로 iOS ViewController 를 만드는 방법을 설명해보겠다.

 

 

 

 

 

 

 

iOS ViewController 생성

1. 먼저 해당 타겟에 새로운 파일을 추가해줘야 하는데 먼저 스토리보드부터 추가해보자!

해당 작업을 완료하고 나면 빈 화면의 UI 가 생성이 되어 있다.

 

Storyboard 클릭!

 

 

 

 

2. 위의 UI 는 기본적으로 UIViewController 가 지정이 되어 있다. 이 UI 를 내 방식대로 관리해줄 커스텀 UiViewController 를 따로 만들어서 지정해 줄 것이다. 이는 Cocoa Touch Class 를 선택하면 아래 두 번째 사진과 같이 자동으로 UIViewController 가 subclass of 로 되어 있는 것을 볼 수 있다.

class 명은 본인이 생성할 뷰컨트롤러명을 지정해주게 되면..

 

 

 

 

 

3. 이렇게 UIViewController 를 확장한 MyViewController 가 생성이 되었다.

 

 

 

 

4. 정말 무지무지 중요한 네번째!.. 아까 생성한 StoryBoard 를 클릭하고 나서 폰 화면 위쪽 노란색? View Controller 를 클릭해주고 오른쪽 화면에서 Custom Class 쪽에 아까 생성했던 View Controller 를 지정해주면 된다. (영상 참고)

 

위에서 설명했던 해당 뷰에 커스텀 뷰 컨트롤러를 지정하는 방법이다. (뷰 컨트롤러는 뷰 조작, 뷰 생명주기 같은 것을 관리하는 것이라고 했다)

 

 

 

 

 

5. 위의 사진을 보면 Custom Class 지정해주는 부분 밑에 Storyboard ID 를 지정해준다. 이로써 iOS 뷰컨트롤러 설정은 끝났다!

 

 

 

 

 

6. 이제 FlutterViewController 에서 iOSViewController 로 이동해보자.

AppDelegate.swfit 파일을 열고 아래와 같이 NavigationController 로 이동하는 코드를 작성하면 된다.

(참고로 메서드 채널에 대한 설명은 생략했는데 이는 인터넷에 찾으면 쉽게 설명되어 있다.)

 

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    private var navigationController: UINavigationController?
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    
    let flutterViewController: FlutterViewController = window?.rootViewController as! FlutterViewController
    
    let nativeChannel = FlutterMethodChannel(name: "com.jihyun/goToNative",
                                            binaryMessenger: flutterViewController.binaryMessenger)
      nativeChannel.setMethodCallHandler({
          (call: FlutterMethodCall, result: FlutterResult) -> Void in
          guard call.method == "goToNative" else {
              result(FlutterMethodNotImplemented)
              return
          }

          let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
          if let container = storyboard.instantiateViewController(withIdentifier: "StoryboardId") as? MyViewController {
              self.navigationController?.pushViewController(container, animated: true) // 이동
          }
      })

 

 

Flutter ViewController -> iOS ViewController 이동은 끝났다.

 

 

 

 

 

 

7. iOS ViewController 에서 Flutter ViewController 로 다시 돌아오려면?

아래와 같이 IOS ViewController 쪽에 버튼을 하나 생성해보자. 이렇게 GUI 방식으로 구성하면 된다!

 

 

 

 

 

 

7. 버튼 액션쪽에 FlutterViewController 로 돌아가게 작성해보자

popToRootViewController

 

import UIKit

class MyViewController: UIViewController {
    
    @IBOutlet weak var button: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    @IBAction func goToNext(_ sender: UIButton) {
        self.navigationController?.popToRootViewController(animated: true)
    }
    
}

 

 

 

 

모두 되었다.

이제 빌드해보면 다음과 같은 화면이 완성된다!

나는 iOS ViewController 쪽에다가 광고 SDK 를 연동해보았다.

 

 

 

<완성 화면>

 

 

 

완성 화면이다.

Flutter View 에서 iOS View 로 이동하는 것 너무 신기하지 않은가??!

 

 

 

 

 

 

구현해본 후기

참고로 나는 앱 개발자로 일하긴 했지만 사실 처음부터 네이티브에 대한 지식은 전무한 편이었고 iOS 공부는 지갑 사정상 적당한 가격의 인강과 인터넷 블로그 글로 공부해서 내가 한 방법이 정확하게 구현한 방법일지는 모른다.

어쨌든 6개월 전 아무것도 몰랐던 나였는데 6개월 후에 iOS 뷰를 구현하고 이를 Flutter 와 연결하게 될 줄 몰랐다. 그동안 iOS 공부한 보람이 느껴져 감동이 벅차올랐고 개인적으로 나 자신에 대해 대견함을 느꼈다.

이번 글을 영상까지 찍어가며 이렇게까지 정성 들여 쓴 이유는 플러터와 iOS 를 연결한 자료가 많이 없는데 Flutter 개발자 출신으로서 같은 Flutter 개발자들이 헤매이지 않고 도움을 주고 싶었다.

이번 구현해 본 프로젝트를 바탕으로 나는 새로운 목표가 생겼다.

Flutter 로 앱을 출시하고 나머지 화면은 iOS 뷰를 만들어볼까 생각하고 있다. 그리고 잘 되면 안드로이드 출시! (= 헛된 망상중..)

Flutter 개발자로 계속 일해도 iOS 공부도 안드로이드 공부도 분야 없이 모두 하고 싶다는 생각과 다짐을 하며 이 글을 마친다.