iOS 프로젝트에 Flutter 모듈 연결하기

Zeto·2023년 5월 3일
0

Swift_Framework

목록 보기
6/6

최근 신규 프로젝트에서 Flutter로 구현된 익명 채팅방 기능을 네이티브 프로젝트에 연결해서 사용해야하는 작업을 진행하게 되었다. 본인도 Flutter 모듈과 연결해보는 것이 처음이었고, Flutter 개발자도 이전에 Android 개발자로서 이번 프로젝트 때 처음으로 Flutter를 작업해본 지라 많은 시행착오를 겪고서 겨우 연결할 수 있었다.
이와 관련한 정보도 생각 외로 적다보니 혹시 모를 도움이 되지 않을까 싶어 관련된 내용을 포스트로 남기고자 생각하였다.

Flutter 모듈 Pod 연결

가장 먼저 해야될 일은 전달받은 Flutter 모듈을 Pod으로 연결하여 Flutter 프레임워크를 import하여 사용할 수 있도록 하는 것이었다.

1) 프로젝트 내 Flutter 모듈 디렉토리 이동

일단 전달받은 Flutter 모듈 디렉토리는 네이티브 프로젝트의 디렉토리와 동일 선상에 두어야 한다.

위의 사진과 같이 기존 프로젝트의 디렉토리와 Flutter 모듈의 디렉토리는 동일한 상위 디렉토리 내에 위치시켜 놓으면 된다.

2) Pod 내용 추가

이제는 기존 네이티브 프로젝트에 생성되어 있던 Podfile 내에 Flutter 모듈의 경로를 설정하고 install할 수 있도록 몇 가지의 내용을 작성해주어야 한다.

flutter_application_path = '../chat_module'
load File.join(flutter_application_path, '.ios', 'Flutter', 'podhelper.rb')

target 'Project' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for Project
  # Flutter
  install_all_flutter_pods(flutter_application_path)
end

post_install do |installer|
  flutter_post_install(installer) if defined?(flutter_post_install)
end

이렇게 내용을 추가하고 pod install 혹은 pod update를 하기만하면 바로 설치!가 되지 않았다. 공식 자료를 포함한 대부분의 자료에서는 본인이 직접 모듈을 만든 상황을 가정하여 설치법을 알려주다보니 본인처럼 다른 작업자에게 모듈을 받아온 상황에 대한 내용이 없어 pod을 설치하는 과정에서부터 막혀버리게 되었다.

2-1) 타인의 Module을 받았을 때, Pod install

이와 같이 타인에게 Module을 받아온 터라 설치를 위해 필요한 podhelper라는 것의 경로가 본인이 설정한 경로와 맞지 않아 에러가 뜨게 된다. 그래서 처음에는 Flutter 모듈 내의 .iOS(숨긴 파일)/Flutter 디렉토리 내에 있는 flutter_export_environment.shGenerated.xcconfig의 Root와 Path 값을 강제로 수정해서 다시 시도해보았다.

그 결과는 당연히 대실패. 분명히 generated된 파일이라 수정하지 말라는 문구가 있음에도 강제로 수정한 자의 비참한 말로였다. 그러던 중, flutter의 명령어 중에 기존에 generated된 내용을 삭제하는 clean과 다시 generate하는 pub get을 알게 되었고 이를 통해 본인의 경로로 다시 generate해보기로 생각했다.

위의 명령어로 새롭게 Flutter 모듈을 generate한 뒤, 다시 네이티브 프로젝트로 돌아와 pod update를 하니 별다른 오류가 없이 설치가 진행되었다. 결국 타인의 경로에 맞춰 generate되었던 모듈을 본인의 경로에 맞춰서 install하려다보니 생긴 문제였다.

이러한 시행착오 끝에 Flutter의 모듈을 네이티브 프로젝트에 import하여 사용할 수 있게 되었다.

Flutter View 연결

1) FlutterAppDelegate 상속

모든 설정이 끝났으니 이제 Flutter 모듈을 import하여 원하는 View를 보여주게끔 설정해주어야 한다. 먼저 AppDelegateFlutterAppDelegate로 바꿔주고, 원하는 View를 보여주기 위한 Engine을 구현하고자 했다.

import UIKit
import Flutter
import FlutterPluginRegistrant

@main
class AppDelegate: FlutterAppDelegate {
    
    private let flutterGroup: FlutterEngineGroup = .init(name: "chat_module", project: nil)
    private(set) var loginEngine: FlutterEngine?
    private(set) var aiEngine: FlutterEngine?
    
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        PushLocationManager.shared.configureFirebase()
        
        loginEngine = flutterGroup.makeEngine(withEntrypoint: nil, libraryURI: nil, initialRoute: "/login")
        aiEngine = flutterGroup.makeEngine(withEntrypoint: nil, libraryURI: nil, initialRoute: "/ai")
        
        guard let loginEngine, let aiEngine else {
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
        
        GeneratedPluginRegistrant.register(with: loginEngine)
        GeneratedPluginRegistrant.register(with: aiEngine)
        
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}

본인의 경우에는 보여줄 화면이 두 개인지라 각각의 엔진을 만들고 이를 Group으로 묶었기에 위와 같이 FlutterEngineGroupmakeEngine(withEntrypoint:, libraryURI:, initialRoute:)을 사용하여 생성해주었다.
특히 makeEngine 메서드를 사용했을 때, initialRoute를 지정해주었는데 이는 멀티 페이지 이동을 할때, 화면에 제일 처음 출력되는 라우트를 불러오는 역할을 해주며 Flutter에서 해당 라우트를 지정해주어야지만 정상적으로 원하는 페이지를 불러올 수 있다.
이와 함께 FlutterAppDelegate를 상속받았을 때, connectingSceneSession을 오버라이드할 수가 없다보니 몇 가지 문제가 발생하였는데 해당 메서드는 작성하지 않아도 Scene을 불러오는데 문제가 없으니 시원하게 제거해주면 된다.

2) FlutterViewController

이제 원하는 VC로 가서 FlutterViewController를 생성하고 해당 화면으로 이동만 해주면 연결 작업이 끝나게 된다.

guard let loginEngine = (UIApplication.shared.delegate as? AppDelegate)?.loginEngine else { return }
                
let flutterVC: FlutterViewController = .init(engine: loginEngine, nibName: nil, bundle: nil)
let loginChannel: FlutterMethodChannel = .init(name: self.channelName, binaryMessenger: flutterVC.binaryMessenger)
                
loginChannel.invokeMethod("getUserToken", arguments: token) { result in
	owner.coordinateDelegate.pushFlutter(with: flutterVC)
}

위와 같이 AppDelegate에서 구현한 엔진을 통해 FlutterViewController를 생성만 해주면 된다. 혹여 본인처럼 FlutterViewController에 전달해야하는 요소가 있거나 네이티브의 고유 기능 등을 사용해야될 필요가 있다면 FlutterMethodChannel을 생성하고 Flutter의 메서드명에 맞춰 invokeMethod를 호출해주면 된다.

마무리

이렇게 정리를 하다보니 별 게 아닌 것 같았는데 처음으로 연결 작업을 진행하다보니 생각 외로 많은 우여곡절을 겪게 되었다. 크로스 플랫폼이 각광을 받으면서 단순히 네이티브 작업 외에도 신경 써야되는 부분들이 많아진다는 걸 뼈저리게 느낀 작업이었다.
추가적으로 Debug 상태로 Run을 하게 되면 Xcode로 Run한 상태에서는 문제 없이 잘 작동하지만, 실기기에서 앱을 눌러 실행할 경우에는 전혀 작동하지 않는 경우가 있다. 이 경우에는 Release로 바꿔서 Run을 한 뒤에 다시 앱을 눌러 실행하면 잘 작동하니 이런 상황을 맞닥뜨린 경우에는 한 번 위와 같은 방법을 시도해보기 바란다.

profile
중2병도 iOS가 하고싶어

0개의 댓글