<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Things take time</title>
    <link>https://g-y-e-o-m.tistory.com/</link>
    <description>열심히는 하는데 잘하는 지는 모르겠는 그런 개발자</description>
    <language>ko</language>
    <pubDate>Sat, 10 Oct 2026 05:15:32 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>겸손할 겸</managingEditor>
    <image>
      <title>Things take time</title>
      <url>https://tistory1.daumcdn.net/tistory/2728149/attach/7eac13261e1e40d782d393721e4d0a01</url>
      <link>https://g-y-e-o-m.tistory.com</link>
    </image>
    <item>
      <title>[Flutter] 화면을 회전하되, 기존 회전값 유지하고 싶을때</title>
      <link>https://g-y-e-o-m.tistory.com/191</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[회전]&lt;/p&gt;
&lt;pre id=&quot;code_1659491449588&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SystemChrome.setPreferredOrientations(
  [DeviceOrientation.landscapeRight]
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter에서 회전을 수행하는 함수다. 해당 함수 호출시, 사용자의 디바이스를 가로로 회전하게 된다. Right이므로 화면 위쪽이 오른쪽으로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[추가]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 함수를 사용하면, 가로로 회전을 강제로 시키기 때문에 사용자가 다시 디바이스를 세로로 회전할 경우, 세로로 돌아가지 않는다. 그러므로 사용자는 다시 버튼을 클릭해서 현재 회전상태에 따라 반대로 회전하는 형태를 취해야 한다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;그래서 저 함수는 보통 앱이 시작되거나, 특정 라우트가 회전 상태를 유지하고싶을 때 사용하는데 나의 경우에는&lt;b&gt; 세로인 경우, 가로로 회전 시키되 사용자가 다시 세로로 기기를 돌리면 자동 회전&lt;/b&gt; 되길 원한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 디바이스가 세로인 상태 -&amp;gt; 버튼 클릭 시 가로로 강제 회전, 가로로 돌렸다가 세로로 디바이스를 회전했을 때 세로도 자동 회전되길 원함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 디바이스가 가로인 상태 -&amp;gt; 버튼 클릭 시 세로로 강제 회전, 세로로 돌렸다가 가로로 디바이스를 회전했을 때 가로도 자동 회전되길 원함&lt;/p&gt;
&lt;pre id=&quot;code_1659491839846&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  SystemChrome.setPreferredOrientations(
    isPortrait
        ? [
            DeviceOrientation.landscapeRight,
          ]
        : [
            DeviceOrientation.portraitUp,
          ],
  ).then((value) {
    SystemChrome.setPreferredOrientations(
      isPortrait
          ? [
              DeviceOrientation.landscapeRight,
              DeviceOrientation.portraitUp,
            ]
          : [
              DeviceOrientation.portraitUp,
              DeviceOrientation.landscapeRight,
            ],
    );
  });&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드는 간단하다. 현재 회전 상태에따라 회전을 바대로 시키고, 해당 함수는 Future함수이므로, 이후에 현재 회전상태에 따라 원상복귀시킨다. 단! 주의해야할 점은, then 이후에 설정되는 회전값의 순서가 중요하다. isPortrait일 경우 landscapeRight를 설정했고, 회전이 수행된 뒤에 다시 landscapeRight, portraitUp을 설정해줘야 회전된 뒤의 회전값이 다시 변하지 않는다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>flutter rotate</category>
      <category>flutter 회전</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/191</guid>
      <comments>https://g-y-e-o-m.tistory.com/191#entry191comment</comments>
      <pubDate>Wed, 3 Aug 2022 10:59:35 +0900</pubDate>
    </item>
    <item>
      <title>[SWIFT] (이것저것) viewDidLoad는 메모리에 할당될 때 호출된다.</title>
      <link>https://g-y-e-o-m.tistory.com/190</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;실제 화면을 addChild로 넣기도 전에 ViewDidLoad가 호출될 때가 있는데, 이는 해당 뷰 컨트롤러 객체를 생성하고, 그에 추가적으로 작업을 수행할 때 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1656393191924&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;let mainViewController = (storyboard.instantiateViewController(withIdentifier: &quot;AAMainViewController&quot;) as! AAMainViewController)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작업은 단순 인스턴스를 선언한 것에 불과하고&lt;/p&gt;
&lt;pre id=&quot;code_1656393236048&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mainViewController.abc()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 해당 인스턴에 접근해서 동작을 호출한다면, 그 때 메모리에 올라가므로 viewDidLoad가 호출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1656393260950&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;self.view.addChild(mainViewController)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 일반적인 방법으로 child로 넣을땐 당연히 viewDidLoad가 호출되지만, 화면에 보이지 않더라도 해당 인스턴스에 접근하여 동작을 수행하면 viewDidLoad가 호출된다.&lt;/p&gt;</description>
      <category>iOS (기능)</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/190</guid>
      <comments>https://g-y-e-o-m.tistory.com/190#entry190comment</comments>
      <pubDate>Tue, 28 Jun 2022 14:15:32 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Flutter와 Native(iOS)간의 통신 방법 - dynamic code</title>
      <link>https://g-y-e-o-m.tistory.com/189</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존 글&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://g-y-e-o-m.tistory.com/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://g-y-e-o-m.tistory.com/184&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655858300914&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Flutter] Flutter안에 Native(iOS)의 UIView 넣는 방법&quot; data-og-description=&quot;[개요] 플러터의 장점은 각 OS별로 짜야하는 UI나 기능 작업들을 하나의 코드인 다트로 작성할 수 있다는 점이다. 다만, 특정한 기능 등은 각 네이티브에서 수행해야할 때가 있다. 마치 하이브리&quot; data-og-host=&quot;g-y-e-o-m.tistory.com&quot; data-og-source-url=&quot;https://g-y-e-o-m.tistory.com/184&quot; data-og-url=&quot;https://g-y-e-o-m.tistory.com/184&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cppe8I/hyOQkatqNg/oj3PQoQWUor2GCK5nKHyh0/img.png?width=800&amp;amp;height=1731&amp;amp;face=0_0_800_1731,https://scrap.kakaocdn.net/dn/bqaiP1/hyOQvXmecX/wjbXKop7HTS9JYK4GVN9k0/img.png?width=800&amp;amp;height=1731&amp;amp;face=0_0_800_1731,https://scrap.kakaocdn.net/dn/fBF6g/hyOQnrulTy/QPH8iZhFqTHKa1XgigHiBK/img.jpg?width=6000&amp;amp;height=4000&amp;amp;face=0_0_6000_4000&quot;&gt;&lt;a href=&quot;https://g-y-e-o-m.tistory.com/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://g-y-e-o-m.tistory.com/184&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cppe8I/hyOQkatqNg/oj3PQoQWUor2GCK5nKHyh0/img.png?width=800&amp;amp;height=1731&amp;amp;face=0_0_800_1731,https://scrap.kakaocdn.net/dn/bqaiP1/hyOQvXmecX/wjbXKop7HTS9JYK4GVN9k0/img.png?width=800&amp;amp;height=1731&amp;amp;face=0_0_800_1731,https://scrap.kakaocdn.net/dn/fBF6g/hyOQnrulTy/QPH8iZhFqTHKa1XgigHiBK/img.jpg?width=6000&amp;amp;height=4000&amp;amp;face=0_0_6000_4000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Flutter] Flutter안에 Native(iOS)의 UIView 넣는 방법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[개요] 플러터의 장점은 각 OS별로 짜야하는 UI나 기능 작업들을 하나의 코드인 다트로 작성할 수 있다는 점이다. 다만, 특정한 기능 등은 각 네이티브에서 수행해야할 때가 있다. 마치 하이브리&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;g-y-e-o-m.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 경우엔, AppDelegate에서의 역할이 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 플러터 플랫폼 뷰를 상속받는 클래스를 연결하고, 메서드 채널을 연결하는 방식의 기초 방법이었는데 이 방법을 적용하니 문제가 발생되었다. 내 경우엔 플러터에서 네이티브 뷰를 UIKitView로 불러와 사용하지만, 다른 플러터 화면에서도 똑같은 뷰를 재사용해야 했다. 다만 전달하는 파라미터만 달라질 뿐이었기 때문에 생각해볼 건 두 가지였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 사용하는 만큼의 NativeView를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 기존 글을 바탕으로 설명한다. GyeomFactory라는 Class를 다른이름으로 생성하는 방법이다. 이런 경우에는 필요한 만큼의 플러터플랫폼 뷰 팩토리를 상속받는 클래스를 계속 생성해야한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 즉, 똑같은 뷰를 재사용해야하는데 그만큼을 생성한다는 것 자체가&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt; 비효율적&lt;/b&gt;&lt;/span&gt;이다. 물론 구현은 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 기존의 GyeomFactory를 재사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글에서 적은 내용처럼 가장 알맞다. 다만, 이 경우에는 플러터에서 뷰가 스택으로 쌓일 경우 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, A라는 라우트(페이지)에서 B라우트를 띄웠을 때(A를 remove하지 않고) A-&amp;gt;B라는 라우트가 쌓여있을 것이다. 근데 A에서도 UIKitView를, B에서도 UIKitView를 공통적으로 사용한다고 가정하자. 그리고 각 UIKitView와 플러터 간의 통신을 위해 메서드 채널도 사용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 메서드 채널에 대해 알아보려면, 메서드 채널 튜토리얼을 보면 이해가 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://flutter-ko.dev/docs/development/platform-integration/platform-channels&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://flutter-ko.dev/docs/development/platform-integration/platform-channels&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1655858798101&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;플랫폼 별 코드 작성&quot; data-og-description=&quot;앱에서 커스텀하게 플랫폼 별 코드를 작성하는 방법을 배워보세요.&quot; data-og-host=&quot;flutter-ko.dev&quot; data-og-source-url=&quot;https://flutter-ko.dev/docs/development/platform-integration/platform-channels&quot; data-og-url=&quot;https://flutter-kr.io/docs/development/platform-integration/platform-channels&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ZMdCr/hyOQwILduZ/O7NcHvdxHKiCQaSO3KvjoK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/OV5pX/hyOQzFsgjZ/UZ2vX3HRVDC1bw36DrcHy1/img.png?width=580&amp;amp;height=647&amp;amp;face=0_0_580_647&quot;&gt;&lt;a href=&quot;https://flutter-ko.dev/docs/development/platform-integration/platform-channels&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://flutter-ko.dev/docs/development/platform-integration/platform-channels&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ZMdCr/hyOQwILduZ/O7NcHvdxHKiCQaSO3KvjoK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/OV5pX/hyOQzFsgjZ/UZ2vX3HRVDC1bw36DrcHy1/img.png?width=580&amp;amp;height=647&amp;amp;face=0_0_580_647');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;플랫폼 별 코드 작성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;앱에서 커스텀하게 플랫폼 별 코드를 작성하는 방법을 배워보세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;flutter-ko.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼에서는 AppDelegate에서 메서드 채널을 생성하여 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 튜토리얼과 내가 올린 글을 접합하게 되면, GyeomFactory라는 클래스를 연결하는 작업과(1), 메서드 채널을 생성하는 작업(2)가 각각 하나씩 있는데, 뷰를 재사용하는 입장에서 메서드 채널을 호출 할 경우 AppDelegate에서는 현재 열린게 B인지, A인지는 모른 상태로 함수를 호출한다. 이런 경우, A/B가 갖고 있는 UIKitView 두개가 이벤트를 호출받아 그에 대한 작업을 수행하는 일이 발생해버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;튜토리얼은 튜토리얼인 만큼 그대로 사용하면 이런 일이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원하는 결과는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;공통적으로 사용하는 네이티브 뷰(GyeomFactory)가 있고, 각 화면에서 파라미터만 다르게 전달할 것이다. 그리고 메서드 채널을 통해 호출할 때는 그 네이티브 뷰는 공통적으로 호출받지 않고 따로 받고싶다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AppDelegate&lt;/h4&gt;
&lt;pre id=&quot;code_1655859066299&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -&amp;gt; Bool {
      GeneratedPluginRegistrant.register(with: self)
      
      // Setting CustomUIView
      weak var pluginRegister = self.registrar(forPlugin: &quot;testPlugin&quot;)
      let factory = GyeomFactory(messenger: pluginRegister!.messenger())
      // testPlugin 이름으로 등록된 단일 플러그인 Context에 factory와 view-type이란 with-id를 등록
      pluginRegister?.register(factory, withId: &quot;view-type&quot;)
      
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 공식 튜토리얼에서 사용한 메서드 채널을 AppDelegate에서 사용하지 않는 것이 중요하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;GyeomFactory&lt;/h4&gt;
&lt;pre id=&quot;code_1655859283269&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 FlutterPlatformView를 상속받는 클래스: 실제 플러터 내에 포함될 UIView를 리턴하는 클래스(func view() -&amp;gt; UIView {}가 프로토콜내 필수 구현함수로 구현되어 있다)
 FlutterPlatformViewFactory를 상속받는 클래스 : FlutterPlatformView를 상속받아 UIView를 리턴하는 그 클래스를 연결하는 클래스(create()가 프로토콜내 필수 구현함수로 등록되어 있다, createArgsCodec()는 optional)
 */
class GyeomFactory: NSObject, FlutterPlatformViewFactory {
    private var messenger: FlutterBinaryMessenger?
    private var gyeomView: GyeomView?
    
    override init() {
        super.init()
    }
    
    init(messenger: FlutterBinaryMessenger) {
        super.init()
        self.messenger = messenger
    }
    
    /**
     Only needs to be implemented if `createWithFrame` needs an arguments parameter.
     Dart에서 파라미터 전달시 필요
     */
    func createArgsCodec() -&amp;gt; FlutterMessageCodec &amp;amp; NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }

    
    func create(
        withFrame frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?
    ) -&amp;gt; FlutterPlatformView {
        let methodChannel = FlutterMethodChannel(name: &quot;method-channel-&quot; + &quot;\(viewId)&quot;, binaryMessenger: messenger!)
        gyeomView = GyeomView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger,
            methodChannel: methodChannel)
        return gyeomView ?? GyeomView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger,
            methodChannel: methodChannel)
    }
}

class GyeomView: NSObject, FlutterPlatformView{
    private var containerView: UIView
    
    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?,
        methodChannel: FlutterMethodChannel
    ) {
        containerView = UIView()
        super.init()
        
        methodChannel.setMethodCallHandler({
            [weak self]
            (call: FlutterMethodCall, result: @escaping FlutterResult) -&amp;gt; Void in
            guard let self = self else { return }
            switch call.method{
            case &quot;mute&quot;:
                result(nil)
                break
            case &quot;start&quot;:
                result(nil)
                break
            case &quot;stop&quot;:
                result(nil)
                break
            default:
                result(FlutterMethodNotImplemented)
                break
            }
        })
    }
    
    func view() -&amp;gt; UIView {
        return containerView
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;view-type&quot;이란 값으로 UIKitView를 처음 만들 때 호출되는 함수인 create에서 viewId는 고유 값으로 등록된다. 그러므로 이 값을 통해 MethodChannel을 등록, 실제 그려지는 뷰인 GyeomView에 넘겨서 setMethodCallHandler를 통해 등록하면 되는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Flutter&lt;/h4&gt;
&lt;pre id=&quot;code_1655860010193&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UiKitView(
      viewType: 'view-type',
      layoutDirection: TextDirection.ltr,
      creationParams: creationParams,
      creationParamsCodec: const StandardMessageCodec(),
      onPlatformViewCreated: (int id) {
        print('[Gyeom] onPlatformViewCreated call :: id: $id');
        _methodChannel = MethodChannel('method-channel-$id');
      },
),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 onPlatfomViewCreated에서 넘겨받는 result의 id가 viewId이므로, 여기서 플러터단의 메서드 채널을 등록하면 이제 플러터와 네이티브 뷰의 연결이 이루어지게 되며, 이 네이티브뷰를 재사용하더라도 처음 UIKitView를 호출하는 시점에 새로운 viewId가 생성되므로 한번의 호출로 여러개의 네이티브 뷰가 호출받게 되는 불상사는 없을 것이다.&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>Flutter NativeView</category>
      <category>Flutter UIKitView MethodChannel</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/189</guid>
      <comments>https://g-y-e-o-m.tistory.com/189#entry189comment</comments>
      <pubDate>Wed, 22 Jun 2022 10:11:13 +0900</pubDate>
    </item>
    <item>
      <title>[SWIFT] NotificationService Extension사용하기, Remote Notification(Silent Push)대신 사용하기, 백그라운드 푸시, 푸시 데이터 저장하기</title>
      <link>https://g-y-e-o-m.tistory.com/188</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;[개요]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 사용자가 푸시를 받았을 때, 포어그라운드 상태라면 AppDelegate내 함수에서 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서버쪽에서 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;사용자에게 푸시를 보냈을때, 특정 값을 저장 했다가 앱이 실행될 때 해당 값을 검사하여 원하는 작업을 수행하길 원한다&lt;/b&gt;&lt;/span&gt;고 하자. 포어그라운드 상태라면 위의 didReceive함수(ios 10미만의 경우, application의 didReceiveRemoteNotification일 것이고 이상인 경우 userNotification의 willPresent, didReceive)에 UserDefaults등을 활용하여 MainViewController나 특정 뷰에서 처리하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 사용자가 앱을 끈 상태(inactive)거나 백그라운드(background)상태일 때는 어떻게 할 것인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 iOS는 백그라운드 작업을 지원하지않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 여기에 추가적으로 애플이 허용한 앱 혹은 설정에 따라 백그라운드 오디오, fetch, remote notification 등을 지원하는데 여기서 고려해볼 만한 것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Silent Push&lt;/b&gt;&lt;/span&gt;, 무음 푸시라하여 Background Mode 내 Remote Notification기능을 활성화하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[사일런트 푸시]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능의 경우, 사용자는 푸시를 볼 수 없다. 그러므로 사용자 입장에서는 푸시에 대해 신경 쓸 필요가 없이 특정한 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 이 기능의 경우 단점이 많은데, 대표적인 예가 푸시발송을 100% 보장하지 않으며, 잦은 푸시를 사용할 경우도 권장하지 않는 등의 단점이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오전 10.58.01.png&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;267&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0TwWO/btrEcQMIAzg/A11Pn2MZ35zDKovkku2CR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0TwWO/btrEcQMIAzg/A11Pn2MZ35zDKovkku2CR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0TwWO/btrEcQMIAzg/A11Pn2MZ35zDKovkku2CR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0TwWO%2FbtrEcQMIAzg%2FA11Pn2MZ35zDKovkku2CR1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;963&quot; height=&quot;267&quot; data-filename=&quot;스크린샷 2022-06-08 오전 10.58.01.png&quot; data-origin-width=&quot;963&quot; data-origin-height=&quot;267&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오전 10.58.13.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFSaYE/btrEdqGF0Wj/g1URld5ZlOspdUQ3yo4Ub1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFSaYE/btrEdqGF0Wj/g1URld5ZlOspdUQ3yo4Ub1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFSaYE/btrEdqGF0Wj/g1URld5ZlOspdUQ3yo4Ub1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFSaYE%2FbtrEdqGF0Wj%2Fg1URld5ZlOspdUQ3yo4Ub1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;351&quot; data-filename=&quot;스크린샷 2022-06-08 오전 10.58.13.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654653517806&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Apple Developer Documentation&quot; data-og-description=&quot;&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&quot; data-og-url=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Apple Developer Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 서버가 푸시를 발송했을 때 알지 못한다. 그러나 앱은 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppDelegate내 함수 중 applicatiom(didReceiveRemoteNotification)함수에서 알 수 있는데, 이 함수는 iOS10 미만의 푸시 접수 함수이기도 하지만,&amp;nbsp; 사일런트 푸시 수신시에도 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사일런트 푸시의 경우, 아래의 조건과 서버 측의 apns payload 양식이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오전 11.00.17.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KaBHb/btrEczj8PER/cRxeNYWIa83Sy0nvkkNBaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KaBHb/btrEczj8PER/cRxeNYWIa83Sy0nvkkNBaK/img.png&quot; data-alt=&quot;Xcode 내 설정&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KaBHb/btrEczj8PER/cRxeNYWIa83Sy0nvkkNBaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKaBHb%2FbtrEczj8PER%2FcRxeNYWIa83Sy0nvkkNBaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;667&quot; height=&quot;224&quot; data-filename=&quot;스크린샷 2022-06-08 오전 11.00.17.png&quot; data-origin-width=&quot;667&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Xcode 내 설정&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1654653651996&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;aps&quot; : {
        &quot;content-available&quot; : 1
    },
    &quot;acme1&quot; : &quot;bar&quot;,
    &quot;acme2&quot; : 42
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력(applicatiom(didReceiveRemoteNotification))&lt;/p&gt;
&lt;pre id=&quot;code_1654653940199&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;didReceiveRemoteNotification userInfo: [AnyHashable(&quot;acme2&quot;): 42, AnyHashable(&quot;acme1&quot;): bar, AnyHashable(&quot;aps&quot;): {
    &quot;content-available&quot; = 1;
}]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** 참고 : 알림 전송 시 payload 양식(payload guide)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654654027656&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Local and Remote Notification Programming Guide: Creating the Remote Notification Payload&quot; data-og-description=&quot;Local and Remote Notification Programming Guide&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html&quot; data-og-url=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gRyad/hyOGObinqj/oOdHvS7DKKSMh0yY7W1ya1/img.png?width=1360&amp;amp;height=448&amp;amp;face=0_0_1360_448&quot;&gt;&lt;a href=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gRyad/hyOGObinqj/oOdHvS7DKKSMh0yY7W1ya1/img.png?width=1360&amp;amp;height=448&amp;amp;face=0_0_1360_448');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Local and Remote Notification Programming Guide: Creating the Remote Notification Payload&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Local and Remote Notification Programming Guide&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사일런트 푸시가 안좋은것은 아니다. 용도에 맞춰서 써야하는게 가장 기본이므로, 시간당 몇 개를 못보낸 다는 이유로 나는 사일런트 푸시 대신 다른 방법을 택했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[NotificationService Extension]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로운 타겟인 익스텐션을 추가해야한다. iOS에서는 다양한 익스텐션이 존재하는데, 예를 들어 설명하자면 웹페이지에서 공유하기를 클릭 했을 때, 카카오톡을 선택하면 카카오톡이 열리면서 친구목록/대화목록 화면이 나오는 것을 알 수 있다. 근데 이 화면은 일반적으로 카카오톡을 켰을때 나오는 화면이랑 다른 화면이다. 즉, 공유하기를 통해 왔을 때 사용자에게 새로운 화면을 중간에 넣어서, 그 화면내에서 독립적인 처리를하는 것인데, 이는 Share Extension이라하며 익스텐션 중 한 종류다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NotificationService Extension이란 기본적으로 사용자에게 푸시 노티를 띄우기 전에 껴드는 것이다. Share의 경우에도 중간에 껴드는 것처럼 같은 맥락으로 이해하면 된다. 푸시를 이쁘게(?) 보내는 앱들을 보면, 노티 안에 그림도 들어가고 클릭 했을 때 이벤트도 있고 하는 경우가 이 익스텐션을 사용한 경우인데, 나의 경우에는 노티를 꾸미기 위해 사용하는 것이 아니라, i&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;nactive/background 상태일 때 푸시를 받고, 그 때 푸시 데이터를 저장하여 AppDelegate에서 뷰로 전달하기 위해 사용하는 용도&lt;/b&gt;&lt;/span&gt;이므로, UI에 대한 설명은 생략한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오전 11.32.11.png&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;208&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n9Fr6/btrEcPG4jnQ/R5H0sc3GqfA5tkCqt8IKkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n9Fr6/btrEcPG4jnQ/R5H0sc3GqfA5tkCqt8IKkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n9Fr6/btrEcPG4jnQ/R5H0sc3GqfA5tkCqt8IKkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn9Fr6%2FbtrEcPG4jnQ%2FR5H0sc3GqfA5tkCqt8IKkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;687&quot; height=&quot;208&quot; data-filename=&quot;스크린샷 2022-06-08 오전 11.32.11.png&quot; data-origin-width=&quot;687&quot; data-origin-height=&quot;208&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오전 9.11.42.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;509&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DbzeJ/btrEhpTWvtY/5ZHar5NDmJfY6iZf7sVn91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DbzeJ/btrEhpTWvtY/5ZHar5NDmJfY6iZf7sVn91/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DbzeJ/btrEhpTWvtY/5ZHar5NDmJfY6iZf7sVn91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDbzeJ%2FbtrEhpTWvtY%2F5ZHar5NDmJfY6iZf7sVn91%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;720&quot; height=&quot;509&quot; data-filename=&quot;스크린샷 2022-06-08 오전 9.11.42.png&quot; data-origin-width=&quot;720&quot; data-origin-height=&quot;509&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 절차를 통해 생성했을 경우, 아래처럼 새로운 디렉토리와 Target도 추가된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오전 11.33.06.png&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;333&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP7itq/btrEcv2u9ZO/OoF2J4h1QOk44e3chqasI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP7itq/btrEcv2u9ZO/OoF2J4h1QOk44e3chqasI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP7itq/btrEcv2u9ZO/OoF2J4h1QOk44e3chqasI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP7itq%2FbtrEcv2u9ZO%2FOoF2J4h1QOk44e3chqasI0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;449&quot; height=&quot;333&quot; data-filename=&quot;스크린샷 2022-06-08 오전 11.33.06.png&quot; data-origin-width=&quot;449&quot; data-origin-height=&quot;333&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NotificationService.swift라는 파일이 생성이 되는데, 이 안에는 didReceive함수와 serviceExtensionTimeWillFire함수가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;didReceive의 경우가 실제 원하는 기능을 구현할때 사용한다. 후자의 경우, 익스텐션이 시스템에의해 사라질때 호출된다는데, 딱히 검증하지 않았다. AppGroupsName은 개발사이트 내 AppGroups에서 생성한 이름이다. 모르겠으면 아래 참고.&lt;/p&gt;
&lt;pre id=&quot;code_1654661215208&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -&amp;gt; Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
        if let bestAttemptContent = bestAttemptContent {
            // Modify the notification content here...
            bestAttemptContent.title = &quot;G-Y-E-O-M&quot;
            bestAttemptContent.body = &quot;B-O-D-Y&quot;
            
            
            if let userDefault = UserDefaults(suiteName: &quot;AppGroupsName&quot;){
                let randomNum = Int.random(in: 0...100)
                userDefault.set(randomNum, forKey: &quot;test&quot;)
                userDefault.synchronize()
            }
            contentHandler(bestAttemptContent)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 부분을 보자, UNMutableNotificationContent 타입의 변수인 bestAttemptContent의 title, body 속성을 변경하면 푸시 노티를 아래와 같이 변경할 수 있다. 이미지 관련 부분도 속성 중 하나일 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_F73700D2-128B-4179-BFA1-F9801020D0BA.png&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KO0NA/btrEhZVfmOF/ifu73kqteJwjrQbw6kgeZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KO0NA/btrEhZVfmOF/ifu73kqteJwjrQbw6kgeZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KO0NA/btrEhZVfmOF/ifu73kqteJwjrQbw6kgeZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKO0NA%2FbtrEhZVfmOF%2Fifu73kqteJwjrQbw6kgeZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1170&quot; height=&quot;395&quot; data-filename=&quot;edited_F73700D2-128B-4179-BFA1-F9801020D0BA.png&quot; data-origin-width=&quot;1170&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든, 중요한 것은 그쪽이 아니고 아래의 userDefault이다. 즉, appGroups로 지정한 UserDefaults의 경우, 실제 저장 장소가 다른 본 앱과 익스텐션 사이의 데이터를 공유할 수 있다. 여기에서 푸시를 받았을 때 노티를 띄우기 전 저장을하고, 본앱의 AppDelegate + ViewControllers에서 가져다 쓰면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 사일런트 푸시의 경우&lt;span&gt; &lt;/span&gt;&quot;content-available&quot; : 1가 들어갔다면, 노티피케이션서비스 익스텐션의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;&quot;mutable-content&quot;: 1&lt;/b&gt; &lt;/span&gt;가 들어가야한다. 예시로 보낸 페이로드의 경우 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1654661441727&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{  
   &quot;aps&quot;:{  
      &quot;sound&quot;:&quot;default&quot;,
      &quot;mutable-content&quot;: 1,
      &quot;alert&quot;:{  
         &quot;body&quot;:&quot;body message&quot;
      }
   }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** AppGroups 등록 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppGroups의 경우 Xcode내에서도 다음과 같이 각각의 Target에서 AppGroups를 +Capability를 눌러서 세팅해야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.13.59.png&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBTasU/btrD8iCQjw3/1YMbZnRCKlqoIR31Alk0y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBTasU/btrD8iCQjw3/1YMbZnRCKlqoIR31Alk0y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBTasU/btrD8iCQjw3/1YMbZnRCKlqoIR31Alk0y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBTasU%2FbtrD8iCQjw3%2F1YMbZnRCKlqoIR31Alk0y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;317&quot; height=&quot;386&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.13.59.png&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.14.04.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDCre4/btrEelkVp4O/Uj97DqsRasxcnq6e1lBI1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDCre4/btrEelkVp4O/Uj97DqsRasxcnq6e1lBI1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDCre4/btrEelkVp4O/Uj97DqsRasxcnq6e1lBI1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDCre4%2FbtrEelkVp4O%2FUj97DqsRasxcnq6e1lBI1K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;318&quot; height=&quot;418&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.14.04.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 AppGroups가 보이지 않는다면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://developer.apple.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.apple.com&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1654661731893&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Apple Developer&quot; data-og-description=&quot;There&amp;rsquo;s never been a better time to develop for Apple platforms.&quot; data-og-host=&quot;developer.apple.com&quot; data-og-source-url=&quot;https://developer.apple.com&quot; data-og-url=&quot;https://developer.apple.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bBO6Nf/hyOG2AK6FF/rHKtgCBemYU5jU0IiuiQj0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qNrXL/hyOGSrk08Q/pR8IK2BZP0zDQx5cgWfm80/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/cvcUBO/hyOG3Gubig/Fa4MkUwfGZV78BKfdDDNAK/img.png?width=1800&amp;amp;height=728&amp;amp;face=0_0_1800_728&quot;&gt;&lt;a href=&quot;https://developer.apple.com&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.apple.com&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bBO6Nf/hyOG2AK6FF/rHKtgCBemYU5jU0IiuiQj0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/qNrXL/hyOGSrk08Q/pR8IK2BZP0zDQx5cgWfm80/img.png?width=1024&amp;amp;height=512&amp;amp;face=0_0_1024_512,https://scrap.kakaocdn.net/dn/cvcUBO/hyOG3Gubig/Fa4MkUwfGZV78BKfdDDNAK/img.png?width=1800&amp;amp;height=728&amp;amp;face=0_0_1800_728');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Apple Developer&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;There&amp;rsquo;s never been a better time to develop for Apple platforms.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.apple.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. account&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Identifiers&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. + 버튼&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.15.59.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;916&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blSyBs/btrEek7pqym/yvECTgivsPAyr6L1FkaQzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blSyBs/btrEek7pqym/yvECTgivsPAyr6L1FkaQzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blSyBs/btrEek7pqym/yvECTgivsPAyr6L1FkaQzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblSyBs%2FbtrEek7pqym%2FyvECTgivsPAyr6L1FkaQzk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;774&quot; height=&quot;916&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.15.59.png&quot; data-origin-width=&quot;774&quot; data-origin-height=&quot;916&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성 후 Xcode에서 AppGroups reload하면 끝이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든, 익스텐션의 didReceive에서 저장이 되었다면 아래와 같이 appDelegate에서 확인할 수 있다.(예)&lt;/p&gt;
&lt;pre id=&quot;code_1654661862861&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    func applicationDidBecomeActive(_ application: UIApplication) {
        print(&quot;applicationDidBecomeActive&quot;)
        if let groupUserDefaults = UserDefaults(suiteName: &quot;AppGroupsName&quot;){
            print(&quot;applicationDidBecomeActive : =&amp;gt; \(groupUserDefaults.value(forKey: &quot;test&quot;))&quot;)
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 아래와 같이 출력되는 걸 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1654661954775&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;applicationDidBecomeActive : =&amp;gt; Optional(90)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;** didReceive에서 print출력 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 익스텐션의 경우 자체 print로 넣는다고 출력되지 않는다. 현재 실행한 타겟은 본앱이기 때문인데, 나는 검수를 꼭 해보고 싶다면 아래와 같이 한다. (NotificationExtension이 본앱, NotificationService가 익스텐션(옆에 E버튼))&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.21.48.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;134&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/egG7CE/btrEgu2AheA/W3KzYUnJGOJc1IXSThMj6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/egG7CE/btrEgu2AheA/W3KzYUnJGOJc1IXSThMj6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/egG7CE/btrEgu2AheA/W3KzYUnJGOJc1IXSThMj6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FegG7CE%2FbtrEgu2AheA%2FW3KzYUnJGOJc1IXSThMj6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;718&quot; height=&quot;134&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.21.48.png&quot; data-origin-width=&quot;718&quot; data-origin-height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.21.58.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;409&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Un13H/btrEcw1GFpL/4KQG0YHtemBHvxvkOitGSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Un13H/btrEcw1GFpL/4KQG0YHtemBHvxvkOitGSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Un13H/btrEcw1GFpL/4KQG0YHtemBHvxvkOitGSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUn13H%2FbtrEcw1GFpL%2F4KQG0YHtemBHvxvkOitGSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;436&quot; height=&quot;409&quot; data-filename=&quot;스크린샷 2022-06-08 오후 1.21.58.png&quot; data-origin-width=&quot;436&quot; data-origin-height=&quot;409&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디버그 실행을 익스텐션인 NotificationService로 시작하고 앱을 선택하는 것에서는 NotificationExtension로 한다. 그리고 d푸시를 발송하고, didReceive에 print문이 정상 출력되는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>iOS (기능)</category>
      <category>NotificationService Extension</category>
      <category>remote push</category>
      <category>silent push</category>
      <category>노티피케이션 익스텐션</category>
      <category>앱 꺼졌을 때 푸시 데이터 저장</category>
      <category>앱 백그라운드 푸시 데이터 저장</category>
      <category>푸시 데이터 저장</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/188</guid>
      <comments>https://g-y-e-o-m.tistory.com/188#entry188comment</comments>
      <pubDate>Wed, 8 Jun 2022 13:24:38 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] iOS 프로젝트에 여러 개의 FlutterViewController 넣기 및 MethodChannel 구성하기(Swift)</title>
      <link>https://g-y-e-o-m.tistory.com/187</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[개요]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글에서 iOS 프로젝트에 플러터 모듈을 import하고 플러터의 뷰 하나를 넣는 테스트를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://g-y-e-o-m.tistory.com/186&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://g-y-e-o-m.tistory.com/186&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1653442966190&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Flutter] iOS 프로젝트에 FlutterViewController넣기(import Flutter Module to Swift)&quot; data-og-description=&quot;[개요] 처음부터 플러터 프로젝트로 만들어서 각각의 네이티브 플랫폼 코드를 사용한다면 플랫폼 채널을 통해 각각의 함수를 호출할 수 있고, FlutterPlatformView를 상속받는 뷰를 넣을수도 있다. 그&quot; data-og-host=&quot;g-y-e-o-m.tistory.com&quot; data-og-source-url=&quot;https://g-y-e-o-m.tistory.com/186&quot; data-og-url=&quot;https://g-y-e-o-m.tistory.com/186&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eMOvLH/hyOxowGQ6T/faD11mhDuD5QmUocO2faD0/img.png?width=291&amp;amp;height=302&amp;amp;face=0_0_291_302,https://scrap.kakaocdn.net/dn/cQ7STz/hyOxmezlnK/bd2nXOHK5ZZ562wz14m2yk/img.png?width=291&amp;amp;height=302&amp;amp;face=0_0_291_302,https://scrap.kakaocdn.net/dn/zyL7I/hyOvXHuzpG/aS7PInCLDKfKpgT87Qk740/img.png?width=828&amp;amp;height=1792&amp;amp;face=0_0_828_1792&quot;&gt;&lt;a href=&quot;https://g-y-e-o-m.tistory.com/186&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://g-y-e-o-m.tistory.com/186&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eMOvLH/hyOxowGQ6T/faD11mhDuD5QmUocO2faD0/img.png?width=291&amp;amp;height=302&amp;amp;face=0_0_291_302,https://scrap.kakaocdn.net/dn/cQ7STz/hyOxmezlnK/bd2nXOHK5ZZ562wz14m2yk/img.png?width=291&amp;amp;height=302&amp;amp;face=0_0_291_302,https://scrap.kakaocdn.net/dn/zyL7I/hyOvXHuzpG/aS7PInCLDKfKpgT87Qk740/img.png?width=828&amp;amp;height=1792&amp;amp;face=0_0_828_1792');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Flutter] iOS 프로젝트에 FlutterViewController넣기(import Flutter Module to Swift)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[개요] 처음부터 플러터 프로젝트로 만들어서 각각의 네이티브 플랫폼 코드를 사용한다면 플랫폼 채널을 통해 각각의 함수를 호출할 수 있고, FlutterPlatformView를 상속받는 뷰를 넣을수도 있다. 그&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;g-y-e-o-m.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 글에서도 밝혔지만, 해당 예제는 러프한 소스여서 이 것을 이해한 것을 바탕으로 코드를 작성하려한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만들고자 하는 목표는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 플러터의 뷰 컨트롤러는 하나 이상을 사용할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 플러터 엔진은 무조건 예열하는 방식으로 사용하며, 직접 선언/호출하는 방법은 지양한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 플러터 뷰 컨트롤러를 넣는 시점에 method channel도 같이 생성하여, Flutter Module &amp;lt;-&amp;gt; Swift간 통신을 설정해 줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전 글의 소스를 참고/비교해서 보도록하자.&lt;/p&gt;
&lt;pre id=&quot;code_1653443353091&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AppDelegate: FlutterAppDelegate {
    let engines = FlutterEngineGroup(name: &quot;multiple-flutters&quot;, project: nil)
    
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
        return true
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1653443261135&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import Flutter
import FlutterPluginRegistrant

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let button = UIButton(type:UIButton.ButtonType.custom)
        button.addTarget(self, action: #selector(showFlutterRoute), for: .touchUpInside)
        button.setTitle(&quot;Show Flutter!&quot;, for: UIControl.State.normal)
        button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
        button.tag = 1
        button.backgroundColor = UIColor.blue
        self.view.addSubview(button)
        
        
        let button2 = UIButton(type:UIButton.ButtonType.custom)
        button2.addTarget(self, action: #selector(showFlutterEntry), for: .touchUpInside)
        button2.tag = 2
        button2.setTitle(&quot;Show Flutter!&quot;, for: UIControl.State.normal)
        button2.frame = CGRect(x: 80.0, y: 260.0, width: 160.0, height: 40.0)
        button2.backgroundColor = UIColor.red
        self.view.addSubview(button2)
    }
    @objc func showFlutterRoute() {
        let flutterVC = GyeomFlutterViewController(withEntrypoint: nil, route: &quot;/gyeom&quot;)
        // let flutterVC = GyeomFlutterViewController(withEntrypoint: &quot;gyeomPage&quot;, route: nil)
        self.present(flutterVC, animated: true, completion: nil)
        
        // Storyboard에서 NavigationController import
        // self.navigationController?.pushViewController(flutterVC, animated: false)
    }
    
    @objc func showFlutterEntry() {
        let flutterVC = GyeomFlutterViewController(withEntrypoint: &quot;gyeomEntry&quot;, route: nil)
        self.present(flutterVC, animated: true, completion: nil)
        
        // Storyboard에서 NavigationController import
        // self.navigationController?.pushViewController(flutterVC, animated: true)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;달라진 점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. firstEngine, secondEngine이 사라짐 =&amp;gt; 사실 필요한 플러터의 뷰 개수만큼 생성하면 되겠지만, 비효율적이므로 사용하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. GeneratedPluginRegistrant.register(with: firstEngine!)을 바로 사용하지 않음 =&amp;gt; AppDelegate에서 미리 등록이 아닌 동적 등록을 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스를 보면 버튼을 클릭했을 때, GyeomFlutterViewController라는 클래스 객체를 생성하고 해당 객체는 withEntryPoint, route의 옵셔널 파라미터를 받는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터의 뷰를 가져와 넣을때, route를 사용하는 방법을 이전글에서 설명했다. 그때 설명하지 못했던 방법은 Entry값을 통해 플러터 뷰를 매핑하는 방법인데, 이 후 다트 소스를 참고하면 될것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1653443509227&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Flutter
import FlutterPluginRegistrant
import Foundation

/**
 @interface FlutterViewController
     : UIViewController &amp;lt;FlutterTextureRegistry, FlutterPluginRegistry, UIGestureRecognizerDelegate&amp;gt;
 #else
 @interface FlutterViewController : UIViewController &amp;lt;FlutterTextureRegistry, FlutterPluginRegistry&amp;gt;
 #endif
 */
class GyeomFlutterViewController: FlutterViewController{
    private var flutterEngine: FlutterEngine?
    private var methodChnnel: FlutterMethodChannel?
    
    init(withEntrypoint entryPoint: String?, route: String?) {
        let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
        flutterEngine = appDelegate.engines.makeEngine(withEntrypoint: entryPoint, libraryURI: nil, initialRoute: route)
        GeneratedPluginRegistrant.register(with: flutterEngine!)
        super.init(engine: flutterEngine!, nibName: nil, bundle: nil)
    }
    
    required init(coder aDecoder: NSCoder) {
        fatalError(&quot;init(coder:) has not been implemented&quot;)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        self.methodChnnel = FlutterMethodChannel(name: &quot;gyeom-channel&quot;, binaryMessenger: self.binaryMessenger)
        // Flutter -&amp;gt; Native Call
        self.methodChnnel?.setMethodCallHandler({
            [weak self]
            (call: FlutterMethodCall, result: @escaping FlutterResult) -&amp;gt; Void in
            guard let self = self else { return }
            print(&quot;GyeomFlutterViewController methodChnnel: \(call.method)&quot;)
        })
        // Native -&amp;gt; Flutter Call
        self.methodChnnel?.invokeMethod(&quot;swift-to-flutter&quot;, arguments: &quot;hello&quot;, result: {
            (result) -&amp;gt; Void in
            print(&quot;swift-to-flutter result: \(result)&quot;)
        })
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터 뷰 컨트롤러를 상속받는 클래스다. 참고로 플러터 뷰 컨트롤러는 UIViewController의 속성을 그대로 가져오면서, 플러터에 대한 추가 프로토콜을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자로 가져온 파라미터를 바탕으로, AppDelegate에 선언한 engines를 가져와 makeEngine을 통해 호출과 동시에 생성, 등록되도록한다. 그리고 뷰가 보여지는 시점에서는 MethodChannel을 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 함으로써, 플러터의 뷰컨트롤러를 생성/등록하고 보여질때 메소드 채널까지 등록되도록하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 ViewController내 버튼을 클릭했을 때, 해당 FlutterViewController객체를 생성하고 present(혹은 push)하여 보여줌과 동시에 메소드채널까지 등록되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Flutter의 dart소스를 보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1653443738682&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() =&amp;gt; runApp(const MyApp());

@pragma('vm:entry-point')
void gyeomEntry() =&amp;gt; runApp(const GyeomEntryPage());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with &quot;flutter run&quot;. You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // &quot;hot reload&quot; (press &quot;r&quot; in the console where you ran &quot;flutter run&quot;,
        // or press Run &amp;gt; Flutter Hot Reload in a Flutter IDE). Notice that the
        // counter didn't reset back to zero; the application is not restarted.
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: {
        '/gyeom': (context) {
          return const GyeomPage();
        },
      },
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. @pragma('vm:entry-point'): 네이티브코드를 사용할 것이라고 컴파일에게 알리는 문법의 구문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gyeomEntry()라는 문법이 iOS의 showFlutterEntry()함수내에 있는 withEntryPoint에 들어가는 파라미터와 일치한다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1653443880689&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - dart-lang/sdk: The Dart SDK, including the VM, dart2js, core libraries, and more.&quot; data-og-description=&quot;The Dart SDK, including the VM, dart2js, core libraries, and more. - GitHub - dart-lang/sdk: The Dart SDK, including the VM, dart2js, core libraries, and more.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md&quot; data-og-url=&quot;https://github.com/dart-lang/sdk&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c3fSxt/hyOxwBviJ1/obQ9qGqNSGO5yblfNElzn1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640&quot;&gt;&lt;a href=&quot;https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/aot/entry_point_pragma.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c3fSxt/hyOxwBviJ1/obQ9qGqNSGO5yblfNElzn1/img.png?width=1280&amp;amp;height=640&amp;amp;face=0_0_1280_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - dart-lang/sdk: The Dart SDK, including the VM, dart2js, core libraries, and more.&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The Dart SDK, including the VM, dart2js, core libraries, and more. - GitHub - dart-lang/sdk: The Dart SDK, including the VM, dart2js, core libraries, and more.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. routes: {'/gyeom': ... } : 이전 글에서 설명한 라우팅 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 플러터 뷰를 띄울 수 있는 2가지의 방법을 모두 소개했고 각각의 GyeomEntryPage()나 GyeomPage()를 구현하면 된다. 단, MethodChannel Name을 맞춰야하는 것을 잊지말자. GyeomPage()의 경우 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1653444116231&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';

class GyeomPage extends StatefulWidget {
  const GyeomPage({Key? key}) : super(key: key);

  @override
  _GyeomPageState createState() =&amp;gt; _GyeomPageState();
}

class _GyeomPageState extends State&amp;lt;GyeomPage&amp;gt; {
  static const platform = MethodChannel('gyeom-channel');

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    _getBatteryLevel();

    platform.setMethodCallHandler(methodHandelr);
  }

  /**
   * Native -&amp;gt; Flutter
   */
  Future&amp;lt;dynamic&amp;gt; methodHandelr(MethodCall methodCall) async {
    print('methodHandelr: ${methodCall.method}');
    if (methodCall.method == &quot;swift-to-flutter&quot;){
      print('methodHandelr: ${methodCall.arguments}');
      return &quot;received flutter&quot;;
    }
  }

  /**
   * Flutter -&amp;gt; Native: 도큐먼트 기본 예제
   */
  Future&amp;lt;void&amp;gt; _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = &quot;Failed to get battery level: '${e.message}'.&quot;;
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Container(
            child: Text('플러터로 만든 페이지.'),
          ),
        ),
      ),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;_getBatteryLevel의 경우 MethodChannel 예제로, 공식 도큐먼트에서 사용하는 함수 그대로이고, methodHandler의 경우 Swift -&amp;gt; Flutter로 함수를 호출할 때 사용하기 위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단히 메소드 채널이름을 분석하면 &lt;b&gt;setMethodHandler&lt;/b&gt;를 쓰는 쪽은 메소드를 받기 위해(리턴은 옵셔널)하는것이고 반대로 메소드를 호출하려면 &lt;b&gt;invokeMethod&lt;/b&gt;를 사용한다.&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>FlutterView MethodChannel</category>
      <category>Swift flutterview</category>
      <category>Swift flutterviewcontroller</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/187</guid>
      <comments>https://g-y-e-o-m.tistory.com/187#entry187comment</comments>
      <pubDate>Wed, 25 May 2022 13:14:13 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] iOS 프로젝트에 Flutter 모듈 import  및 FlutterViewController 넣기(import Flutter Module to Swift/add FlutterView)</title>
      <link>https://g-y-e-o-m.tistory.com/186</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[개요]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터 플러터 프로젝트로 만들어서 각각의 네이티브 플랫폼 코드를 사용한다면 플랫폼 채널을 통해 각각의 함수를 호출할 수 있고, FlutterPlatformView를 상속받는 뷰를 넣을수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데&amp;nbsp;만약 기존에 iOS/Android로 개발되어 있는 앱에, 플러터로 된 뷰를 넣고 싶다면? 그 플러터로 된 모듈을 공통으로 작업해서 배포하며 사용하도록하고, 이외에는 각 OS별로 개발하고싶다면 이 글을 참고하도록하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/add-to-app/ios/project-setup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.flutter.dev/development/add-to-app/ios/project-setup&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1653353631904&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Integrate a Flutter module into your iOS project&quot; data-og-description=&quot;Learn how to integrate a Flutter module into your existing iOS project.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/development/add-to-app/ios/project-setup&quot; data-og-url=&quot;https://docs.flutter.dev/development/add-to-app/ios/project-setup&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ucMkE/hyOvUbXrlx/yiZDIOnOBVYJXkMwj98NwK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/jeZon/hyOvObKd67/z9ww2PuHzO3UiAZOEVho9k/img.png?width=2574&amp;amp;height=1190&amp;amp;face=0_0_2574_1190,https://scrap.kakaocdn.net/dn/kd6aC/hyOvPPhRmo/LF0NeDRcpHMtsc9iKv2CQk/img.png?width=2266&amp;amp;height=1000&amp;amp;face=0_0_2266_1000&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/add-to-app/ios/project-setup&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/development/add-to-app/ios/project-setup&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ucMkE/hyOvUbXrlx/yiZDIOnOBVYJXkMwj98NwK/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461,https://scrap.kakaocdn.net/dn/jeZon/hyOvObKd67/z9ww2PuHzO3UiAZOEVho9k/img.png?width=2574&amp;amp;height=1190&amp;amp;face=0_0_2574_1190,https://scrap.kakaocdn.net/dn/kd6aC/hyOvPPhRmo/LF0NeDRcpHMtsc9iKv2CQk/img.png?width=2266&amp;amp;height=1000&amp;amp;face=0_0_2266_1000');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Integrate a Flutter module into your iOS project&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to integrate a Flutter module into your existing iOS project.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen?tab=initial-route-swift-tab&quot;&gt;https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen?tab=initial-route-swift-tab&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1653353274619&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Adding a Flutter screen to an iOS app&quot; data-og-description=&quot;Learn how to add a single Flutter screen to your existing iOS app.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen?tab=initial-route-swift-tab&quot; data-og-url=&quot;https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/iauMT/hyOvPV1yJi/2FoBhEVovCcM8kXIcecOl0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen?tab=initial-route-swift-tab&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/development/add-to-app/ios/add-flutter-screen?tab=initial-route-swift-tab&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/iauMT/hyOvPV1yJi/2FoBhEVovCcM8kXIcecOl0/img.png?width=937&amp;amp;height=461&amp;amp;face=0_0_937_461');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Adding a Flutter screen to an iOS app&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to add a single Flutter screen to your existing iOS app.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 공홈대로 보면서 하면 되긴하는데, 참고용으로 작성하는 포스팅!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 중간 내용들이 이해가 안되서 삽질하며 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[Create Flutter Module And Add Modult to iOS Project]&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 아래의 명령어를 통해 플러터 모듈 템플릿을 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1653353670433&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd some/path/
flutter create --template module my_flutter&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;some/path/는 개인적으로 지정하면되나, 나같은 경우에는 이미 iOS프로젝트가 있다 가정했으므로, iOS프로젝트와 같은 경로로 두었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-23 오전 9.33.54.png&quot; data-origin-width=&quot;291&quot; data-origin-height=&quot;302&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ri2HL/btrCXmySJp1/ig7GJbanYs8nMdJeUM30t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ri2HL/btrCXmySJp1/ig7GJbanYs8nMdJeUM30t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ri2HL/btrCXmySJp1/ig7GJbanYs8nMdJeUM30t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRi2HL%2FbtrCXmySJp1%2Fig7GJbanYs8nMdJeUM30t0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;291&quot; height=&quot;302&quot; data-filename=&quot;스크린샷 2022-05-23 오전 9.33.54.png&quot; data-origin-width=&quot;291&quot; data-origin-height=&quot;302&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MyApp은 iOS프로젝트, my_flutter는 플러터 모듈이다. module이란 공통 디렉토리에 두 개의 프로젝트가 있는 셈.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-23 오전 10.25.16.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bT8qTc/btrC0N9SAKz/KZdq3MPlxFvVOFFKGoutPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bT8qTc/btrC0N9SAKz/KZdq3MPlxFvVOFFKGoutPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bT8qTc/btrC0N9SAKz/KZdq3MPlxFvVOFFKGoutPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbT8qTc%2FbtrC0N9SAKz%2FKZdq3MPlxFvVOFFKGoutPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;316&quot; height=&quot;159&quot; data-filename=&quot;스크린샷 2022-05-23 오전 10.25.16.png&quot; data-origin-width=&quot;316&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 my_flutter디렉토리 내 히든 폴더들을 살펴보면 .ios폴더가 보인다. 원래 플러터 프로젝트를 생성하면 android/ios와 같은 폴더들이 자동적으로 생성되는데, 모듈로 생성했기 때문에 이처럼 보인다. Podfile에서 보겠지만, 실제 iOS는 다른 프로젝트에 있기때문에 이 지점과 실제 프로젝트간 연결을 하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 실제 iOS프로젝트와 플러터 모듈간의 연결을 위해 다음과 같은 Podfile을 수정한다. 참고로 Podfile이 없는 기본프로젝트(워크스페이스 파일이 없는 경우)이라면 다음과 같이 Podfile을 생성해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1653354095321&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd some/path/
touch Podfile
open -e Podfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://guides.cocoapods.org/using/using-cocoapods.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://guides.cocoapods.org/using/using-cocoapods.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1653354130334&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CocoaPods Guides&quot; data-og-description=&quot;CocoaPods is fully open-sourced, so it depends on community contributions to get better. If you're looking to start working on CocoaPods, this is the place to start.&quot; data-og-host=&quot;guides.cocoapods.org&quot; data-og-source-url=&quot;https://guides.cocoapods.org/using/using-cocoapods.html&quot; data-og-url=&quot;https://guides.cocoapods.org&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://guides.cocoapods.org/using/using-cocoapods.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://guides.cocoapods.org/using/using-cocoapods.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CocoaPods Guides&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;CocoaPods is fully open-sourced, so it depends on community contributions to get better. If you're looking to start working on CocoaPods, this is the place to start.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;guides.cocoapods.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Podfile을 다음과 같이 작성한다.(platform과 target은 알맞게)&lt;/p&gt;
&lt;pre id=&quot;code_1653354152860&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;platform :ios, '9.0'

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

target 'MyApp' do
  install_all_flutter_pods(flutter_application_path)
end

post_install do |installer|
  flutter_post_install(installer) if defined?(flutter_post_install)
end&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;platform 버전은 개발할 프로젝트 deployment target과 맞추고, target도 프로젝트명으로 맞추면 된다. 위의 Podfile은 아무런것도 넣지 않은 기본 Podfile 템플릿이다. 그리고 아래의 명령어를 호출하면 기본 설정은 끝이다.&lt;/p&gt;
&lt;pre id=&quot;code_1653354234905&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pod install&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Podfile을 살펴보면, my_flutter라는 path에서 Flutter/podhelper.rb라는 파일과 join한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 flutter_pods를 설치하는 것으로 보이는데 위에서 말한 히든파일을 열어보면 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-24 오전 10.00.16.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYGl9X/btrCY20kahj/fk7rRKfUV9tyoS2k7hsqd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYGl9X/btrCY20kahj/fk7rRKfUV9tyoS2k7hsqd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYGl9X/btrCY20kahj/fk7rRKfUV9tyoS2k7hsqd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYGl9X%2FbtrCY20kahj%2Ffk7rRKfUV9tyoS2k7hsqd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;292&quot; height=&quot;595&quot; data-filename=&quot;스크린샷 2022-05-24 오전 10.00.16.png&quot; data-origin-width=&quot;292&quot; data-origin-height=&quot;595&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 저 Flutter/podhelper.rb파일이 보이는데 이 파일이 실제 iOS프로젝트와 연결하는 것으로 보인다. 여기까지가 플러터 모듈을, 껍데기를 씌워주는 기본 작업은 완료다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[Add Flutter View]&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 플러터의 뷰를 iOS프로젝트에 넣어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. FlutterEngine 예열하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터엔진이란 객체를 미리 예열하라는 식으로 적혀있는데, Flutter UI를 표시할때는 적지않은 대기 시간비용이 있다고 한다. 그래서 플러터 엔진을 미리 시작하면 이 비용을 절약할수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppDelegate의 didFinishLaunchingWithOptions에서 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1653355123253&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import Flutter
// Used to connect plugins (only if you have plugins with iOS platform code).
import FlutterPluginRegistrant

@UIApplicationMain
class AppDelegate: FlutterAppDelegate { // More on the FlutterAppDelegate.
  lazy var flutterEngine = FlutterEngine(name: &quot;my flutter engine&quot;)

  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
    // Runs the default Dart entrypoint with a default Flutter route.
    flutterEngine.run();
    // Used to connect plugins (only if you have plugins with iOS platform code).
    GeneratedPluginRegistrant.register(with: self.flutterEngine);
    return super.application(application, didFinishLaunchingWithOptions: launchOptions);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터 모듈이 정상적으로 Pod에 들어왔다면, import가 가능해지고 코드를 보면 기본 iOS AppDelegate의 상속 부분이 달라지는데, FlutterAppDelegate를 들어가서 보면&lt;/p&gt;
&lt;pre id=&quot;code_1653355223034&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@interface FlutterAppDelegate
    : UIResponder &amp;lt;UIApplicationDelegate, FlutterPluginRegistry, FlutterAppLifeCycleProvider&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 있던UIResponder&lt;span&gt;, &lt;/span&gt;UIApplicationDelegate에 FltuterAppLifeCycleProvider가 추가적으로 있는 것으로 알 수 있다. Swift는 다중상속을 지원하지않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, UIApplicationDelegate/FltuterAppLifeCycleProvider는 클래스가 아닌 프로토콜이기 때문에 다중상속처럼 보이는 것이지만, 어쨌든 기존 상속부분을 FlutterAppDelegate로 통째로 교체하거나, 아니면 기존 상속에 FltuterAppLifeCycleProvider 프로토콜을 추가적으로 상속받아 추가적으로 함수를 오버라이드하여 적용할 수 있다.(위 가이드 링크 참고)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든, 예열이란 작업은 FlutterEngine의 instance를 먼저 선언하고, 사용하란 의미와 같다. 사용방법은 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1653366508080&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import Flutter

class ViewController: UIViewController {
  override func viewDidLoad() {
    super.viewDidLoad()

    // Make a button to call the showFlutter function when pressed.
    let button = UIButton(type:UIButton.ButtonType.custom)
    button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
    button.setTitle(&quot;Show Flutter!&quot;, for: UIControl.State.normal)
    button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
    button.backgroundColor = UIColor.blue
    self.view.addSubview(button)
  }

  @objc func showFlutter() {
    let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).flutterEngine
    let flutterViewController =
        FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
    present(flutterViewController, animated: true, completion: nil)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스를 보면, iOS프로젝트 내 뷰 컨트롤러에 버튼이 있고 버튼 클릭이벤트인 showFlutter만 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AppDelegate에 선언된 flutterEngine이란 변수를 가져와, 해당 엔진을 바탕으로 생성된 FlutterViewController 객체인 flutterViewController를 present로 띄우는 간단한 소스다. 이 작업을 수행하면, my_flutter의 main()함수에 있는 run()에 있는 해당 화면을 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 위의 showFlutter대신 아래와 같은 직접 호출로도 할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1653366607384&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Existing code omitted.
func showFlutter() {
  let flutterViewController = FlutterViewController(project: nil, nibName: nil, bundle: nil)
  present(flutterViewController, animated: true, completion: nil)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 이처럼 사용할 경우는 AppDelegate에 선언된 FlutterEngine객체를 사용하지않고 직접 사용하기 때문에, 권장하진 않는다. flutter화면이 사용되는 경우가 정말 적은 경우나, 뷰 컨트롤러와 플러터 뷰컨트롤러간의 상태를 유지할 필요가 없을 경우 등에 사용한다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;[Custom]&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 위의 코드까지는 기본예제와 다름없다. 순서를 정리해보자면, AppDelegate에서 FlutterEngine 인스턴스를 선언해주고, 가져다 쓸 곳에서 해당 엔진을 바탕으로 FlutterViewController객체를 생성해주고, 해당 화면을 present하여 새창으로 띄운다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 생각해 볼것이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1. FlutterEngine객체는 AppDelegate에서만 사용가능한 것인가 -&amp;gt; X&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 ViewController에서 FlutterViewController를 생성하기 전에 바로 선언하여 사용해도 된다. 실제 도큐먼트에서도 AppDelegate에서 선언하는 것도 한 가지 방법이라 설명하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2. 위의 예제는 main()에 있는 기본 뷰를 띄워주는데, 만약 Flutter에서 만든 다른 화면이 있고 다른 화면을 띄울수 있는가 -&amp;gt; O&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flutterEngine객체의 run부분을 다음과 같이 사용하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1653367178406&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        flutterEngine.run(withEntrypoint: nil, initialRoute: &quot;/gyeom&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 /gyeom으로 라우팅된 위치로 엔진이 실행된다. 당연하겠지만, 이는 플러터에서도 코드 작업을 해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1653367235485&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void main() =&amp;gt; runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with &quot;flutter run&quot;. You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // &quot;hot reload&quot; (press &quot;r&quot; in the console where you ran &quot;flutter run&quot;,
        // or press Run &amp;gt; Flutter Hot Reload in a Flutter IDE). Notice that the
        // counter didn't reset back to zero; the application is not restarted.
        primarySwatch: Colors.blue,
      ),
      initialRoute: '/',
      routes: {
        '/gyeom': (context) {
          return const GyeomPage();
        }
      },
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 처럼, /gyeom이란 라우팅 주소로 넘어오면 &lt;span style=&quot;color: #006dd7;&quot;&gt;&lt;b&gt;GyeomPage()&lt;/b&gt;&lt;/span&gt;를 호출하고 있다. MyHomePage()가 아님을 유의하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;3. 만약, 여러개의 플러터 화면을 띄우고 싶다면 FlutterEngine객체의 run부분을 재정의하여 initialRoute부분을 바꾸면 되는가? -&amp;gt; X&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 버튼이 여러 개이고, 각각의 버튼에서는 플러터에서 만든 화면중 하나 하나에 매핑을 하고싶다면, 아래와 같이 사용을 생각해 볼 수 있다.&lt;s&gt;(삽질)&lt;/s&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1653367441167&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;        flutterEngine.run(withEntrypoint: nil, initialRoute: &quot;/gyeom&quot;)
        
        ..
        
       
        flutterEngine.run(withEntrypoint: nil, initialRoute: &quot;/&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로, 사용하는 위치에 하나의 flutterEngine을 불러와 재정의하며 라우팅할 수 있을 거라 생각했으나 불가하다. 내가 이해한 바로는 각 플러터 엔진에는 하나의 라우팅만 허용되므로 아래와 같이 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1653368191588&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class AppDelegate: FlutterAppDelegate {
    let engineGroup = FlutterEngineGroup(name: &quot;my flutter engine group&quot;, project: nil)
    var firstEngine: FlutterEngine?
    var secondEngine: FlutterEngine?
    
    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -&amp;gt; Bool {
        
        firstEngine = engineGroup.makeEngine(withEntrypoint: nil, libraryURI: nil, initialRoute: &quot;/gyeom&quot;)
        secondEngine = engineGroup.makeEngine(withEntrypoint: nil, libraryURI: nil, initialRoute: &quot;/&quot;)
        
        GeneratedPluginRegistrant.register(with: firstEngine!)
        GeneratedPluginRegistrant.register(with: secondEngine!)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlutterEngineGroup이란 것을 만들고, 해당 인스턴스에서 makeEngine을 호출하여 각각의 엔진을 생성, GeneratedPluginRegistrant으로 등록한다. 저 makeEngine이란 함수에 들어가보면&lt;span&gt;&lt;span&gt; &lt;/span&gt;&lt;/span&gt;&lt;b&gt;Creates a running `FlutterEngine` that shares components with this group.&lt;/b&gt; 으로 이 그룹으로 묶인 것들은 컴포넌트를 공유하며 running중인 FlutterEngine을 생성한다 되어있다. 그래서 그런지 이전에 했던 코드처럼 flutterEngine.run(with..) 함수를 호출하지 않고 makeEngine으로도 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 코드는 러프하게 되어있으므로, 엔진 개수나 등록같은 경우엔 클래스내 생성자를 통해 동적으로 만들어 지게 놓는 것이 가장 좋다. 단순히 보기 좋게 AppDelegate내 하드코드로 작성되었음을 참고하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 ViewController에서는 아래와 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1653368518597&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        // Make a button to call the showFlutter function when pressed.
        let button = UIButton(type:UIButton.ButtonType.custom)
        button.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
        button.setTitle(&quot;Show Flutter!&quot;, for: UIControl.State.normal)
        button.frame = CGRect(x: 80.0, y: 210.0, width: 160.0, height: 40.0)
        button.tag = 1
        button.backgroundColor = UIColor.blue
        self.view.addSubview(button)
        
        
        let button2 = UIButton(type:UIButton.ButtonType.custom)
        button2.addTarget(self, action: #selector(showFlutter), for: .touchUpInside)
        button2.tag = 2
        button2.setTitle(&quot;Show Flutter!&quot;, for: UIControl.State.normal)
        button2.frame = CGRect(x: 80.0, y: 260.0, width: 160.0, height: 40.0)
        button2.backgroundColor = UIColor.red
        self.view.addSubview(button2)
    }

    @objc func showFlutter(sender: UIButton) {
        if sender.tag == 1{
          if let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).firstEngine{
                let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
                present(flutterViewController, animated: true, completion: nil)
            }
        }else{
            if let flutterEngine = (UIApplication.shared.delegate as! AppDelegate).secondEngine{
                let flutterViewController = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
                present(flutterViewController, animated: true, completion: nil)
            }
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 하면, 각 버튼을 누를때 마다 플러터로 만든 각기 다른 화면이 노출될 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;AFA61CF8-9305-48C8-9564-766AED7A53C5.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wKvQo/btrC1ToZqnG/giffAm2INTKbWKszbZ42FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wKvQo/btrC1ToZqnG/giffAm2INTKbWKszbZ42FK/img.png&quot; data-alt=&quot;앱 실행했을 때의 ViewController내 두개의 버튼&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wKvQo/btrC1ToZqnG/giffAm2INTKbWKszbZ42FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwKvQo%2FbtrC1ToZqnG%2FgiffAm2INTKbWKszbZ42FK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;866&quot; data-filename=&quot;AFA61CF8-9305-48C8-9564-766AED7A53C5.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;앱 실행했을 때의 ViewController내 두개의 버튼&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;922D3FA9-0592-4315-92F4-591E9D56EED6.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crpb2p/btrC2g5hpR3/iEIfhOkq9oanKM6uzaFwM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crpb2p/btrC2g5hpR3/iEIfhOkq9oanKM6uzaFwM0/img.png&quot; data-alt=&quot;첫 번째 버튼 클릭시, AppDelegate내 firstEngine(&amp;quot;/gyeom&amp;quot;)으로 호출이므로, GyeomPage()라는 위젯이 나온다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crpb2p/btrC2g5hpR3/iEIfhOkq9oanKM6uzaFwM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcrpb2p%2FbtrC2g5hpR3%2FiEIfhOkq9oanKM6uzaFwM0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;866&quot; data-filename=&quot;922D3FA9-0592-4315-92F4-591E9D56EED6.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;첫 번째 버튼 클릭시, AppDelegate내 firstEngine(&quot;/gyeom&quot;)으로 호출이므로, GyeomPage()라는 위젯이 나온다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;9E434B4C-6D1E-4CD1-B5E4-C5E72CD99577.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEimow/btrC0MDRYuo/ihXzN482tEEs7tSt2I7XNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEimow/btrC0MDRYuo/ihXzN482tEEs7tSt2I7XNK/img.png&quot; data-alt=&quot;두 번째 버튼 클릭시, AppDelegate내 secondEngine(&amp;quot;/&amp;quot;)으로 호출이므로, 기본 템플릿 화면이 나온다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEimow/btrC0MDRYuo/ihXzN482tEEs7tSt2I7XNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEimow%2FbtrC0MDRYuo%2FihXzN482tEEs7tSt2I7XNK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;866&quot; data-filename=&quot;9E434B4C-6D1E-4CD1-B5E4-C5E72CD99577.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두 번째 버튼 클릭시, AppDelegate내 secondEngine(&quot;/&quot;)으로 호출이므로, 기본 템플릿 화면이 나온다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>fluterviewcontroller swift</category>
      <category>flutter module</category>
      <category>native flutter view</category>
      <category>플러터 모듈</category>
      <category>플러터 뷰 컨트롤러</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/186</guid>
      <comments>https://g-y-e-o-m.tistory.com/186#entry186comment</comments>
      <pubDate>Tue, 24 May 2022 14:12:47 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 잡다한 플러터 이것저것(계속 수정/추가)</title>
      <link>https://g-y-e-o-m.tistory.com/185</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;컨테이너 안에 있는 위젯들을 고려한, 현재 컨테이너 너비 구하기코드는 다음과 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1652746483692&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  Size _getSize(GlobalKey key) {
    if (key.currentContext != null) {
      final RenderBox renderBox =
          key.currentContext!.findRenderObject() as RenderBox;
      return renderBox.size;
    }
    return Size(0, 0);
  }&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Stack의 사이즈는 Children중 가장 큰 위젯의 사이즈로 정해진다, 다만 Positioned처럼 지정되지 않은 Container가 있을 경우, 해당 컨테이너가 다른 위젯들 무시하고 해당 컨테이너가 모든것을 차지한다.&lt;/li&gt;
&lt;li&gt;앱을 처음 키자마자 회전을 원할 때는 다음과 같이 사용한다. ensureInitialized는 시스템에게 이후에 비동기작업이 있을 것임을 알리는 것이고, main()에서 사용할때만 사용하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1652747679321&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  WidgetsFlutterBinding.ensureInitialized();
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.landscapeLeft])
      .then((_) {
    runApp(const MyApp());
  });&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FlutterAppDelegate는 기존 AppDelegate +FlutterAppLifeCycleProvider의 추가 버전이다.&lt;/li&gt;
&lt;li&gt;FlutterEngine은 플러터 화면을 네이티브에 띄울 때 사용되는 개념이다.&lt;/li&gt;
&lt;li&gt;Command PhaseScriptExecution failed with a nonzero exit code. 오류: 문법오류, 플러터 쪽에서 살펴보거나 엑스코드에서 보여주는것 확인할 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-05-25 오전 10.21.45.png&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;61&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGirfI/btrC6HPhGqE/7bngjLcFkzNnb4sIJKR3O0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGirfI/btrC6HPhGqE/7bngjLcFkzNnb4sIJKR3O0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGirfI/btrC6HPhGqE/7bngjLcFkzNnb4sIJKR3O0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGirfI%2FbtrC6HPhGqE%2F7bngjLcFkzNnb4sIJKR3O0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;471&quot; height=&quot;61&quot; data-filename=&quot;스크린샷 2022-05-25 오전 10.21.45.png&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;61&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Failed to register observatory port with mDNS with error -65555. : info plist 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;&amp;lt;key&amp;gt;NSBonjourServices&amp;lt;/key&amp;gt;
&amp;lt;array&amp;gt;
    &amp;lt;string&amp;gt;_dartobservatory._tcp&amp;lt;/string&amp;gt;
&amp;lt;/array&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Widget 내에서 if문/for문을 사용해야할 경우 다음과 같이 사용할 수 있다. 2가지 방법(...[] / 혹은 []{}없이 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1653467018272&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            if (contentPanelList.length &amp;gt; 1) ...[
              for (ContentPanel contentPanel in contentPanelList) ...[
                Visibility(
                  visible: _isPortrait,
                  child: _setDataListView(),
                ),
              ]
            ],
            if (contentPanelList.length &amp;gt; 1)
              for (ContentPanel contentPanel in contentPanelList)
                Visibility(
                  visible: _isPortrait,
                  child: _setDataListView(),
                ),&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter에서 String -&amp;gt; DateFormat 은 다음과 과정을 거치는데, intl라이브러리를 사용한다. 또한, 라이브러리가 기본적으로 지원하는 Date형식이 있어서 아래에 해당되지 않는String값이라면 String의 값을 바꿔서, 해당 함수에서 이해할수 있게 해줘야한다.(?)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1653547573722&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&quot;2012-02-27 13:27:00&quot;
&quot;2012-02-27 13:27:00.123456789z&quot;
&quot;2012-02-27 13:27:00,123456789z&quot;
&quot;20120227 13:27:00&quot;
&quot;20120227T132700&quot;
&quot;20120227&quot;
&quot;+20120227&quot;
&quot;2012-02-27T14Z&quot;
&quot;2012-02-27T14+00:00&quot;
&quot;-123450101 00:00:00 Z&quot;: -12345년.
&quot;2002-02-27T14:00:00-0500&quot;: 와 같음&quot;2002-02-27T19:00:00Z&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 서버에서 20120227132700, 흔히 yyyyMMddhhmmss의 방식으로 내려줬다고 하자. 위의 Format중일치하는 것이 없으므로, 여기서 DateFormat('').parse(String)사용시 불가하다. 그러므로 가장 비슷한 20120227T132700 저 방식처럼 중간에 T라는 문자열을 넣어서 변환해줘야 정상적으로 작동한다.&lt;/p&gt;
&lt;pre id=&quot;code_1653547776191&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:intl/intl.dart' as intl;
...


String onairDate = '202205261549';
String formattedString = onairDate.substring(0, 8) + 'T' + onairDate.substring(8, onairDate.length);
var formattedOnairDate = intl.DateFormat('yyyy.MM.dd HH:mm').format(DateTime.parse(formattedString)).toString();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 얼마나 거지같은지, 좀 더 간단한건 없으려나.. 서버에서 자리수에 대해 확실히 내려준다면 난 그냥 문자열을 편집해서 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt; iOS 기기로 플러터를 디버그하면, 디버그 껐을 때 단독으로 쓸 수 없으므로 flutter run --release를 통해 상용 방식으로 디버그하여 확인한다.&lt;/li&gt;
&lt;li&gt;ListView를 통해 위젯을 만들었을 때, 굉장히 버벅이는 것이 느껴질 땐 위 방법처럼 release를 통해 상용 방식으로 디버그하면 빨라진다. 디버깅이 느리다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1653548482309&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Why my flutter app's listview scroll not as smooth as the flutter gallery app?&quot; data-og-description=&quot;I created a release build of my scroll demo app using Flutter. I wonder why the listview scrolling of my app is NOT as smooth as the flutter gallery app. I used LG G5 for this test. Here's a link...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app&quot; data-og-url=&quot;https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bbcLn7/hyOyqAWnBn/CkhaJQvBAN6GaCkyGUrET0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/46761036/why-my-flutter-apps-listview-scroll-not-as-smooth-as-the-flutter-gallery-app&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bbcLn7/hyOyqAWnBn/CkhaJQvBAN6GaCkyGUrET0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Why my flutter app's listview scroll not as smooth as the flutter gallery app?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I created a release build of my scroll demo app using Flutter. I wonder why the listview scrolling of my app is NOT as smooth as the flutter gallery app. I used LG G5 for this test. Here's a link...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flutter에서 안드로이드 match_parent, wrap_content의 대응 되는 개념은 각각, Container기준 사이즈를 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;max로 지정(double.infinite)&lt;/b&gt;&lt;/span&gt;하느냐 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;지정하지 않느냐&lt;/span&gt;&lt;/b&gt;이다. 따로 값을 지정하지 않으면, 안에 들어있는 위젯의 크기에 따라 각각 늘어난다.&lt;/li&gt;
&lt;li&gt;Column, Row에서는 안에 들어오는 children들의 크기에 맞춰 자동적으로 늘어나거나 줄어들지만 리스트뷰와 같은 위젯(스크롤)이 들어올땐 지정해야한다. 예를 들어, Column에서 Text부분과 ListView가 들어올 경우, 두 위젯 모두 height가 지정되어 있지 않다면 에러가 발생한다. ListView를 부모의 나머지를 갖도록 Expand로 감싸고, 부모의 Height를 지정해야한다. 혹은 ListView를 SizeBox로 감싼 뒤, 리스트뷰의 크기를 지정한다.&lt;/li&gt;
&lt;li&gt;리스트 뷰를 쓸 때는 Height가 필요하다. 그러므로 Column, Row에서는 위의 방법을, 단독 사용시엔 height를 지정하자.&lt;/li&gt;
&lt;li&gt;빈 위젯, Empty widget을 리턴하고 싶을 땐 다음과 같이 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1653635134286&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SizedBox.shrink();&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ShrinkWrap속성은 ScrollDirection의 속성을 가진 위젯(ListView, GridView, CustomScrillView)이 자신이 스크롤할 수 있는 범위를, Child인 위젯의 내용에 따라 결정되어야하는지에 대한 여부이다. default false의 경우엔 스크롤 방향 기준, 허용된 최대 사이즈를 가진다(부모 위젯의 사이즈 만큼). 만약 ScrollDirection의 속성을 가진 위젯이 제한되지 않은 제약조건(Expand..)을 가지고 있다면 true를 사용해야 unbounded height runtime exception이 나지않는다.&lt;/li&gt;
&lt;li&gt;Column Children에서 height를 가진 위젯과 Expanded인 위젯이 있고, 그 Expanded 위젯 안에서 Column이 또 있고, 같은 방식으로 height를 가진위젯과 Expanded를 가진 위젯이 있을 때, Expanded위젯에 ListView가 있다면 문제가 되지 않는다. 그런데 위의 경우에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;ListView와 같은 Depth에 있는 height를 가진 위젯을 하나의 묶음으로 봐서 Column자체를 Scroll하고 싶다&lt;/b&gt;&lt;/span&gt;면 다음과 같이 해야한다.&lt;br /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;ListView의 shrinkWrap속성을 true로 변경한다. -&amp;gt; 즉, 자신의 부모위젯 사이즈 만큼을 가지는 것이 아니라 ListView안에 return되는 Widget의 사이즈만큼을 가지겠다고 변경한다.&lt;/li&gt;
&lt;li&gt;ListView의 physics를 NeverScrollableScrollphysics()로 변경한다. -&amp;gt; 리스트뷰 스크롤이 아니라 Column자체에서 스크롤을 해야하므로, 리스트 뷰 자체의 스크롤을 사용하지 않는다.&lt;/li&gt;
&lt;li&gt;ListView를 감싸고 있는 Expanded대신 Flexible로 변경한다. Expanded는 Flexible위젯에서 tight한 위젯이다. default인 loose로 변경함으로써 남아 있는 빈 공간을 하위 위젯인 리스트뷰의 크기만큼을 사용하겠다고 하는 것이다. 이때, 리스트뷰의 크기가 남은 빈 공간보다 크더라도 에러가 발생하지 않으며, 남아있는 부분 중 리스트뷰가 가질 수 있는 것만큼을 가진다. tight는 하위 위젯의 크기와 관련없이 남은 공간 자체를 가져가겠다는 의미이다.&lt;/li&gt;
&lt;li&gt;Column을 SingleChildScrollView로 감싸고, mainAxisSize를 min으로 한다.&lt;/li&gt;
&lt;li&gt;참고: flexible은 shrinkWrap의 true, mainAxisSize.min과 쌍으로 맞고, expanded는 false와 맞다. (의미 다시 한 번 생각)&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;initState, didChangeDependencies 모두 처음 생성될 때 한 번 호출되지만, didChangeDependencies는 context에 접근할 수 있는 차이가 있다.&lt;/li&gt;
&lt;li&gt;setState(){}를 호출할 때는 mounted를 체크하는 것이 좋다. setState(){}에만 쓰는 것은 번거로우므로 setState()를 override하여 if(mounted){ super.setState() } 를 사용하도록 하자.&lt;/li&gt;
&lt;li&gt;플러터에서 기본적으로 사용중인 위젯트리가 변경되지 않는다면 setState()를 해도 겉모양만 변할 뿐 내용은 변하지 않는다, 그런데 안의 child가 추가되거나 트리상 위치가 바뀌면, 다시 그리게 되는데 이를 방지하려면 재생성안되게 할 위젯에 key를 선언한다.(Unique Key사용 금지, setState할 때마다 키가 유니크하게 다시 생성되므로) 이렇게 되면, 해당 key값을 검사하여 같은 key를 가진 위젯이라면 다시 그리지 않고 재사용하게 된다.&lt;/li&gt;
&lt;li&gt;TextButton의 패딩을 제거하기위해선 다음과 같이 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1659921685761&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;            TextButton(
              style: TextButton.styleFrom(
                primary: Colors.white,
                tapTargetSize: MaterialTapTargetSize.shrinkWrap,
                padding: EdgeInsets.zero,
              ),
              onPressed: () {
              },
              child: Icon(Icons.clear),
            ),&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Key는 stateless, stateful 위젯 모두에서 사용할 수 있으며 위젯의 설정 값중 하나이다. 동일한 key값을 가진 위젯을 재배치할 경우, 해당 위젯은 재사용된다.&lt;/li&gt;
&lt;li&gt;Stateless에서 동일한 위젯의 하위트리를 가진 상태에서 build가 다시 실행된다면, 해당 위젯들은 재사용 된다.(build는 호출되지만, 안의 내용들은 바뀌지 않음)&lt;/li&gt;
&lt;li&gt;Stateless에서 위젯 앞에 const를 붙일 경우, 불필요한 build가 되지 않는다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Stack에서 위치가 바뀌는 위젯의 경우(Depth가 동일하나 배치가 바뀌는 경우) Key를 사용해야 재사용되면서 rebuild되지 않는다. =&amp;gt; key의 위치는 재사용 되는 위젯 자체&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Stack이 아닌 Depth자체가 바뀌는 위젯의 경우 Key를 사용해도 적용되지 않는다.&lt;/b&gt;&amp;nbsp;Depth는 동일하되, 위젯의 배치가 바뀌는 경우엔 Key를 사용하고 Depth가 바뀌는 위젯의 경우에는 Key를 사용해도 재사용되지 않는다. 해당 Depth에 동일하게 위젯이 배치되어야한다. const사용은 sense.&lt;/li&gt;
&lt;li&gt;Provider의 경우 상위에서 선언한(Consumer or ChangeNotifier)프로바이더의 생성자는 한 번 호출되나 build에서 해당 프로바이더에 다시 접근하는 인스턴스를 아래처럼 선언하는 경우, 생성자는 다시 호출되지 않으나 그 안에 사용되는 변수들은 다 초기화된다. 이유는 생성자는 상위 위젯에서 한번 선언하므로 한번만 호출되는 것이고, 이후 build에서 해당 인스턴스를 접근하게 되면 생성자에서 초기화 되지 않은 애들은 처음 선언된 그 변수로 초기화된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;build(BuildContext context){
...

_providerChattingSendbird =
    Provider.of&amp;lt;ProviderChattingSendbird&amp;gt;(context, listen: true);
_providerDetailCommon =
    Provider.of&amp;lt;ProviderDetailCommon&amp;gt;(context, listen: true);&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sizedbox vs container: 둘다 width, height, child를 가질 수 있지만, sizedbox는 const로 인식한다. 이 뜻은 화면을 다시 build하는 과정에서 const는 바뀌지 않으므로 sizedbox밑에 있는 위젯은 다시 그리지 않는다. 예를 들어, 채팅방을 만들었을 때 화면을 회전했고 그에 따라 rebuild하는 과정에서 채팅 뷰를 sizedbox 하위로 만들었으면 채팅이 유지되는 것으로 보일것이고 container의 경우 다시 그리기 때문에 채팅방 load부터 다시 수행한다. 그러므로 sizedbox의 사용을 자주하자.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Flutter</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/185</guid>
      <comments>https://g-y-e-o-m.tistory.com/185#entry185comment</comments>
      <pubDate>Tue, 17 May 2022 10:01:53 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Flutter안에 Native(iOS)의 UIView 넣는 방법</title>
      <link>https://g-y-e-o-m.tistory.com/184</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;[개요]&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러터의 장점은 각 OS별로 짜야하는 UI나 기능 작업들을 하나의 코드인 다트로 작성할 수 있다는 점이다. 다만, 특정한 기능 등은 각 네이티브에서 수행해야할 때가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마치 하이브리드 앱처럼 웹뷰와 네이티브 사이간 interface를 구성하여 작성하는 것처럼, Flutter와 네이티브간에도 이런 방법이 가능하다. 대표적인게 MethodChannel을 이용한 전달이다. 이 방법은 비동기를 통한 콜백처리 까지 가능한데, 이 글에서는 MethodChannel이 아니라 뷰 객체(UIView)를 Flutter안에 넣고 싶을 때다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 도큐먼트에서는 구글맵과 같은 뷰를 각 OS별로 API연동 및 작성하고, 각각의 뷰를 플러터안에 넣는 방법으로 소개되어있는데, 한글 문서로 된 것은 많이 없어서 내가 정리하려한다. 그리고 공식 문서에서 말하는 공식 단어 등은 솔직히 이해가 그리 잘 되지 않는 편이라서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/platform-integration/platform-views&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.flutter.dev/development/platform-integration/platform-views&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1651457751763&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Hosting native Android and iOS views in your Flutter app with Platform Views&quot; data-og-description=&quot;Learn how to host native Android and iOS views in your Flutter app with Platform Views.&quot; data-og-host=&quot;docs.flutter.dev&quot; data-og-source-url=&quot;https://docs.flutter.dev/development/platform-integration/platform-views&quot; data-og-url=&quot;https://docs.flutter.dev/development/platform-integration/platform-views&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.flutter.dev/development/platform-integration/platform-views&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.flutter.dev/development/platform-integration/platform-views&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Hosting native Android and iOS views in your Flutter app with Platform Views&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Learn how to host native Android and iOS views in your Flutter app with Platform Views.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.flutter.dev&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 공식문서 위치는 링크로 두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후에 MethodChannel이란 글을 쓰겠지만, 나중에 하고보면 둘의 사용 코드만 다를 뿐 비슷하단 느낌을 받았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[사용 방법]&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;In Flutter&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 패키지를 import한다.&lt;/p&gt;
&lt;pre id=&quot;code_1651562045045&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';;
import 'package:flutter/foundation.dart';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 UIView가 들어갈 위젯쪽에 아래의 코드를 참고한다.&lt;/p&gt;
&lt;pre id=&quot;code_1651562171536&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class _PracticeMainState extends State&amp;lt;PracticeMain&amp;gt; {
  static const String VIEW_TYPE = 'gyeom-type';

  // Pass parameters to the platform side.
  static const Map&amp;lt;String, dynamic&amp;gt; creationParams = &amp;lt;String, dynamic&amp;gt;{
    &quot;param&quot;: &quot;겸식이&quot;
  };

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: PreferredSize(
          preferredSize: Size.fromHeight(0),
          child: AppBar(),
        ),
        body: SafeArea(
          child: _setPlatformView(),
        ),
      ),
    );
  }

  Widget _setPlatformView() {
    switch (defaultTargetPlatform) {
      case TargetPlatform.iOS:
        return Container(
          width: double.maxFinite,
          height: 300,
          child: UiKitView(
            viewType: VIEW_TYPE,
            layoutDirection: TextDirection.ltr,
            creationParams: creationParams,
            creationParamsCodec: const StandardMessageCodec(),
          ),
        );
      case TargetPlatform.android:
        return Container(
          child: Center(
            child: Text('Android'),
          ),
        );
      default:
        return Container();
    }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 간단히 설명하자면, 컬럼 방식으로 된 위젯이 있으며 이 위젯은 _getPlayerController라는 함수에서 리턴받은 위젯 하나를 가진 컬럼위젯이다. _getPlayerController에서는 UIKitView라는 위젯을 Child로 가진 컨테이너 위젯을 리턴한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIEW_TYPE이란 변수안에 들어가는 값과, Map형(Swift의 Dictionary에 대응)에 대응하는 파라미터를 UIKitView라는 위젯의 생성자로 넣어주면 되는 것이다. 그래서 플러터에서 할 일은, Swift에서 맞춰줄 키 값과 파라미터들을 세팅해주는 것이다. 그리고 그걸 매치되는 Swift쪽에서 해당 값을 바탕으로 세팅하면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;In iOS(AppDelegate)&lt;/h3&gt;
&lt;pre id=&quot;code_1651563260722&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -&amp;gt; Bool {
    GeneratedPluginRegistrant.register(with: self)
      
      let factory = GyeomFactory()
      self.registrar(forPlugin: &quot;GyeomPlugin&quot;)?.register(factory, withId: &quot;gyeom-type&quot;)
      
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter로 앱을 만들면 기본으로 생성되는 AppDelegate내 소스다. FlutterAppDelegate를 상속받아 사용하는데, 여기서 눈여겨 볼 것은 GyeomFactory라는 클래스명과, widId 파라미터에 들어가는 저 값을 주목하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;self.registrar라는 함수는 FlutterAppDelegate내 FlutterPlguinRegistry라는 프로토콜 내에 갖고 있는 함수인데, 해석 그대로 등록해주는 사람(registrar)에게 withId란 키 값을 가진 팩토리를 -&amp;gt; GyeomPlugin라는 이름으로 가진 플러그인 내에 넣고, 이 플러그인을 등록해 달라. 이런 의미로 해석할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 플러그인이라는 것은 다트 기준 패키지의 한 종류이다. 여기서 다트로만 패키지를 만들었느냐, 위처럼 스위프트나 코틀린처럼 다양한 언어로 작성됐느냐에 따라 플러그인의 기준으로 나눈다고 한다. 현재 이 프로젝트를 패키지로 만든다거나 연동, 작성하지 않기 때문에 저 플러그인 값은 현재 예제에서는 아무런 값이 들어가도 상관없다. 이후 다트 패키지를 만들때 사용할것 같은데 지금 예제에서는 아무런 값을 입력하자.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;In iOS(FLNativeViewFactory)&lt;/h3&gt;
&lt;pre id=&quot;code_1651565227354&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import Foundation
import Flutter


/**
 FlutterPlatformViewFactory를 상속받는 클래스 : FlutterPlatformView를 상속받아 UIView를 리턴하는 그 클래스를 연결하는 클래스(create()가 프로토콜내 필수 구현함수로 등록되어 있다, createArgsCodec()는 optional)
 */
class GyeomFactory: NSObject, FlutterPlatformViewFactory{
    private var gyeomView: GyeomView?
    private var messenger: FlutterBinaryMessenger?

    /**
     Only needs to be implemented if `createWithFrame` needs an arguments parameter.
     Dart에서 파라미터 전달시 필요
     */
    func createArgsCodec() -&amp;gt; FlutterMessageCodec &amp;amp; NSObjectProtocol {
        return FlutterStandardMessageCodec.sharedInstance()
    }
    
    override init(){
        super.init()
    }
    
    init(messenger: FlutterBinaryMessenger) {
        self.messenger = messenger
        super.init()
    }
    
    func callGyeomViewMethod(){
        if let gyeomView = gyeomView {
            gyeomView.receiveGyeomViewMethod()
        }
    }

    func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -&amp;gt; FlutterPlatformView {
        
        self.gyeomView = GyeomView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger)
        return gyeomView ?? GyeomView(
            frame: frame,
            viewIdentifier: viewId,
            arguments: args,
            binaryMessenger: messenger)
    }
    
    
    
}

/**
FlutterPlatformView를 상속받는 클래스: 실제 플러터 내에 포함될 UIView를 리턴하는 클래스(func view() -&amp;gt; UIView {}가 프로토콜내 필수
 구현함수로 구현되어 있다)
 */
class GyeomView: NSObject, FlutterPlatformView{
    private var returnView: UIView?
    var nativeLabel = UILabel()
    
    
    override init() {
        returnView = UIView()
        super.init()
    }
    
    
    init(
        frame: CGRect,
        viewIdentifier viewId: Int64,
        arguments args: Any?,
        binaryMessenger messenger: FlutterBinaryMessenger?
    ) {
        returnView = UIView()
        super.init()
        // iOS views can be created here
        createNativeView(view: returnView!, args: args)
    }
    
    func view() -&amp;gt; UIView {
        return returnView!
    }
    
    func receiveGyeomViewMethod(){
        print(&quot;receiveGyeomViewMethod&quot;)
    }
    
    func createNativeView(view _view: UIView, args: Any?){
        print(&quot;createNativeView args: \(args)&quot;)
        _view.backgroundColor = UIColor.blue
        nativeLabel.text = &quot;이것은 GyeomView&quot;
        nativeLabel.textColor = UIColor.white
        nativeLabel.textAlignment = .center
        nativeLabel.frame = CGRect(x: 0, y: 0, width: 180, height: 48.0)
        nativeLabel.translatesAutoresizingMaskIntoConstraints = false
        _view.addSubview(nativeLabel)
        nativeLabel.centerXAnchor.constraint(equalTo: _view.centerXAnchor).isActive = true
        nativeLabel.centerYAnchor.constraint(equalTo: _view.centerYAnchor).isActive = true
        
        
        if let args = args {
            if let param = args as? [String:Any]{
                print(&quot;param: \(param[&quot;param&quot;] ?? &quot;파라미터 없음&quot;)&quot;)
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 해석은 저 위에 주석부분을 보면 된다. 이런 함수들을 구현할땐 상속받는 클래스, 프로토콜을 꼭 들어가서 보자. 보면, 왜 func view()라는 함수가 덩그러니 있는지, create(...)는 무슨 함수인지인지에 대해 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여튼 여기서 볼 것은, returnView라는 객체가 플러터의 UIKitView라는 위젯에 들어가 사용자에게 보여지는 것으로 이해하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;print문을 보면 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1652746168455&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;createNativeView args: Optional({
    param = &quot;\Uacb8\Uc2dd\Uc774&quot;;
})
param: 겸식이&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;80267267-47FA-43D0-A596-CE9D98926CE9.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bC3ebF/btrCiQVyP6l/YcDKdqIH9KUXN9RKIZBwT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bC3ebF/btrCiQVyP6l/YcDKdqIH9KUXN9RKIZBwT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bC3ebF/btrCiQVyP6l/YcDKdqIH9KUXN9RKIZBwT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbC3ebF%2FbtrCiQVyP6l%2FYcDKdqIH9KUXN9RKIZBwT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;828&quot; height=&quot;1792&quot; data-filename=&quot;80267267-47FA-43D0-A596-CE9D98926CE9.png&quot; data-origin-width=&quot;828&quot; data-origin-height=&quot;1792&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;[초기 UI를 그릴때 주의사항]&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 return되는 뷰의 경우, 바로 문제가 없지만 만약 UI를 그리는데 비동기적인 작업이 들어가거나 할경우에는, initView에서 작업을 수행하는 것을 지양하도록하자. Flutter에서는 UIKitView를 호출했고, iOS단에서는 작업자가 지정한 뷰를 리턴하는데 만약 지정한 뷰에 특정 시간작업이 필요하다면 플러터에서 UIKitView를 호출하는 것과 initView에서의 작업이 충돌하여 잘 그려지지 않는 문제가 발생할 수 있기 때문이다. 이럴때는 Flutter쪽에서 UIKitView내에 있는 onPlatformViewCreated를 사용하도록하자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1655168001730&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	UiKitView(
              viewType: _viewType,
              layoutDirection: TextDirection.ltr,
              creationParams: creationParams,
              creationParamsCodec: const StandardMessageCodec(),
              onPlatformViewCreated: (int id) {
                print('[Gyeom] UiKitView onPlatformViewCreated aw:$id');
                _startPlayer();
              },
            ),&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Flutter</category>
      <category>flutter platformview</category>
      <category>flutter uikitview</category>
      <category>플러터 add uiview</category>
      <category>플러터 UIView</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/184</guid>
      <comments>https://g-y-e-o-m.tistory.com/184#entry184comment</comments>
      <pubDate>Tue, 17 May 2022 09:12:13 +0900</pubDate>
    </item>
    <item>
      <title>[SWIFT] URL형식으로 된 String에서 Parameter 추출</title>
      <link>https://g-y-e-o-m.tistory.com/183</link>
      <description>&lt;pre id=&quot;code_1637295591221&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;func getQueryStringParameter(url: String, param: String) -&amp;gt; String? {
    guard let url = URLComponents(string: url) else {
        return nil
    }
    return url.queryItems?.first(where: { $0.name == param })?.value
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[예시]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;print(&quot;[Gyeom] :: \(getQueryStringParameter(url: &quot;&lt;a href=&quot;https://g-y-e-o-m.tistory.com/manage/newpost/?type=post&amp;amp;returnURL=%2Fmanage%2Fposts%2F,&quot;&gt;https://g-y-e-o-m.tistory.com/manage/newpost/?type=post&amp;amp;returnURL=%2Fmanage%2Fposts%2F&quot;&lt;/a&gt;, param: &quot;returnURL&quot;)&quot;&lt;/p&gt;</description>
      <category>iOS (기능)</category>
      <category>swift url parameter</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/183</guid>
      <comments>https://g-y-e-o-m.tistory.com/183#entry183comment</comments>
      <pubDate>Fri, 19 Nov 2021 13:21:17 +0900</pubDate>
    </item>
    <item>
      <title>[Xcode] The linked and embeded framework '' is missing one or more architectures required by this target: armv7</title>
      <link>https://g-y-e-o-m.tistory.com/182</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[이슈]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 업체에게 프레임워크를 전달받아 import하여 사용하고있는데, 빌드나 컴파일은 문제가 없다가 아카이브 도중 위와 같은 오류가 발생했다. 해석해보자면 해당 프레임워크가 target: armv7를 지원하는 구조가 빠져있다는 식으로 해석이 되는데 이는, 프레임워크를 제작할 때, 빌드를 어떻게 해서 줬는지에 대한 이슈일 확률이 크다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-09-27 오후 6.18.30.png&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kJnzk/btrgbKt6SOk/eFEkgUxmQ58Q3g38OSu3u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kJnzk/btrgbKt6SOk/eFEkgUxmQ58Q3g38OSu3u0/img.png&quot; data-alt=&quot;빌드할 때나 아카이브할 때 사용하는 곳&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kJnzk/btrgbKt6SOk/eFEkgUxmQ58Q3g38OSu3u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkJnzk%2FbtrgbKt6SOk%2FeFEkgUxmQ58Q3g38OSu3u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;382&quot; height=&quot;116&quot; data-filename=&quot;스크린샷 2021-09-27 오후 6.18.30.png&quot; data-origin-width=&quot;382&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;빌드할 때나 아카이브할 때 사용하는 곳&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이곳에서 Any iOS Device를 선택하지 않고 만들어서 프레임워크를 넘겨줬을 경우, 넘겨받은 프로젝트의 배포 환경과 비교하여 다른경우 위와 같은 오류가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[해당 프레임워크가 armv7을 지원하지 않는다는 것을 확인하는 방법]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널을 실행하여, 해당 프레임워크 경로를 이동한 다음 cd명령어를 통해 프레임워크 내부로 이동한다. 이동한 다음 아래의 명령어를 입력하면 현재 열려있는 프레임워크가 지원하는 아키텍쳐를 알 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1632734472631&quot; class=&quot;swift&quot; data-ke-language=&quot;swift&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;file '프레임워크명'&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-09-27 오후 6.21.40.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CfNbx/btrf9DbrR4A/At7ipkPC0Cf3Zn2ZvZXML0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CfNbx/btrf9DbrR4A/At7ipkPC0Cf3Zn2ZvZXML0/img.png&quot; data-alt=&quot;확인해보니 arm64에 대한 지원만 하는 것으로 확인 되었다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CfNbx/btrf9DbrR4A/At7ipkPC0Cf3Zn2ZvZXML0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCfNbx%2Fbtrf9DbrR4A%2FAt7ipkPC0Cf3Zn2ZvZXML0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;916&quot; height=&quot;38&quot; data-filename=&quot;스크린샷 2021-09-27 오후 6.21.40.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;확인해보니 arm64에 대한 지원만 하는 것으로 확인 되었다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당 업체에 전화했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[출처]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무나 똑같은 상황이었던 분의 티스토리!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://littleshark.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://littleshark.tistory.com/49&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1632734561781&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;iOS에 특정 프레임워크, 라이브러리가 빌드 되지  않도록 하는 법&quot; data-og-description=&quot;최근에 회사작업을 하면서 외부 업체의 프레임워크를 앱에 넣어야 하는 일이 생겼습니다. 원래 실 디바이스를 물려서 작업하지만 그렇다보니 책상도 지저분해지고 그래서 시뮬레이터로 작업하&quot; data-og-host=&quot;littleshark.tistory.com&quot; data-og-source-url=&quot;https://littleshark.tistory.com/49&quot; data-og-url=&quot;https://littleshark.tistory.com/49&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bJNlim/hyLLDQHjYm/akdscsJnWfb1pxrKKQSSu0/img.png?width=800&amp;amp;height=165&amp;amp;face=0_0_800_165,https://scrap.kakaocdn.net/dn/bjCf8y/hyLKkL0iaC/xiECx3xksb35kcT9ruKHTk/img.png?width=800&amp;amp;height=165&amp;amp;face=0_0_800_165,https://scrap.kakaocdn.net/dn/oJbaM/hyLKnhCZeK/5IBTLRtbzl2NLoZ78cK9Yk/img.png?width=1533&amp;amp;height=257&amp;amp;face=0_0_1533_257&quot;&gt;&lt;a href=&quot;https://littleshark.tistory.com/49&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://littleshark.tistory.com/49&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bJNlim/hyLLDQHjYm/akdscsJnWfb1pxrKKQSSu0/img.png?width=800&amp;amp;height=165&amp;amp;face=0_0_800_165,https://scrap.kakaocdn.net/dn/bjCf8y/hyLKkL0iaC/xiECx3xksb35kcT9ruKHTk/img.png?width=800&amp;amp;height=165&amp;amp;face=0_0_800_165,https://scrap.kakaocdn.net/dn/oJbaM/hyLKnhCZeK/5IBTLRtbzl2NLoZ78cK9Yk/img.png?width=1533&amp;amp;height=257&amp;amp;face=0_0_1533_257');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;iOS에 특정 프레임워크, 라이브러리가 빌드 되지 않도록 하는 법&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근에 회사작업을 하면서 외부 업체의 프레임워크를 앱에 넣어야 하는 일이 생겼습니다. 원래 실 디바이스를 물려서 작업하지만 그렇다보니 책상도 지저분해지고 그래서 시뮬레이터로 작업하&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;littleshark.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>XCODE</category>
      <category>he linked and embeded framework</category>
      <category>is missing one or more architectures required by this target</category>
      <author>겸손할 겸</author>
      <guid isPermaLink="true">https://g-y-e-o-m.tistory.com/182</guid>
      <comments>https://g-y-e-o-m.tistory.com/182#entry182comment</comments>
      <pubDate>Mon, 27 Sep 2021 18:22:51 +0900</pubDate>
    </item>
  </channel>
</rss>