iOS Custom Widget 만들기

1 답변 글타래를 보이고 있습니다
  • 글쓴이
    • 뚭니
      참가자
      • 글작성 : 9
      • 답글작성 : 17

      아이폰을 사용하던 중에 문득 위젯은 어떻게 만드는지 궁금해졌다.
      Today Extension Target을 추가해서 Custom Widget 을 만드는 과정을 정리해봤다.

      Contents

      1 단계 iOS App Extension 이란 무엇인가?

      1. 애플에서 허용한 익스텐션 종류

      • Share
        • By providing more sharing options, iOS and macOS enable your app to share photos, videos, websites, and other content with users on social networks and other sharing services.
      • Today
        • Your apps can now display widgets in the Today view of Notification Center, providing quick updates or enabling brief tasks. For example, posting updates on package deliveries, the latest surf reports, or breaking news stories.
      • Photo Editing
        • Embed your filters and editing tools directly into the Photos or Camera app, so users can easily apply your effects to images and videos.
      • Custom Keyboard
        • With iOS, you can provide custom keyboards with different input methods and layouts for users to install and use systemwide.
      • File Provider
        • You can now provide a document storage location that can be accessed by other apps. Apps that use a document picker view controller can open files managed by the storage provider or move files into the storage provider.
      • Action
        • Create your own custom action buttons in the Action sheet to let users watermark documents, add something to a wish list, translate text to a different language, and more.
      • Document Provider
        • If you offer remote storage of a user’s iOS documents, you can create an app extension that lets users directly upload and download documents in any compatible app.
      • Finder Sync
        • Badge local macOS folders to let users know the status of items that are remotely synced. You can also implement contextual menus to let users directly manage their synced content.
      • Audio
        • With Audio Unit Extensions, you can provide audio effects, sound generators, and musical instruments that can be used by Audio Unit host apps and distributed via the App Store.

      2. Extension Container

      정확한 용어는 Extension Container 이고 다음과 같은 특징을 가진다.

      • 앱이 아니다.
        • life cycle 과 environment 가 완전히 다르다.
      • 애플 framework 코드를 통해서만 접근된다.
        • 애플 framework 을 통해서만 호출되는 기능들의 집합이며, 호스트앱(host app)이 다이렉트로 익스텐션을 호출하지 못한다.
        • 이 사실만 제외하면 앱처럼 유저 인터페이스도 가질 수 있고, 이를 위한 ViewController, 리소스 등 모두 사용 가능하다.
      • App to app IPC (Inter-Process Communication)가 아니다.
      • 빌드될 때 추가적인 타겟을 통해 따로 빌드되며 설치될 때는 앱과 같이 설치, 삭제될 때도 앱과 함께 삭제된다.
        • 바이너리 자체도 앱과 독립적이다.
      • 실행 시에도 앱과는 완전히 다른 독립 프로세스(process)로 실행되기 때문에 완전히 다른 주소공간(Isolated address space)을 가지게 된다.


      출처 | Understand How an App Extension Works
      출처 | MacOS 10.10 & iOS 8 새기능 익스텐션(Extensions) 개념 잡기

      2 단계 Today Extension 타겟 추가하기

      Xcode 프로젝트 생성한 후 File > New > Target
      스크린샷 2020-03-06 오후 2 44 15 스크린샷 2020-03-06 오후 2 44 28
      iOS 에서 제공되는 Application Extension 을 확인할 수 있다.
      스크린샷 2020-03-06 오후 6 14 21
      Today Extension 을 검색해서 추가한다.
      스크린샷 2020-03-06 오후 2 46 33
      Embed In Application 에는 기본적으로 HOST APP(메인 앱)이 선택되어있다.
      스크린샷 2020-03-06 오후 6 29 33
      Target 에 Today Extension 이 추가되었는데, 이는 기본적으로 Notification Center framework 내에서 제공하는 기능임을 알 수 있다.
      스크린샷 2020-03-06 오후 6 27 18
      Today Extension 의 Display Name 부분에 적는 이름이 실제 앱의 위젯 항목에 나타난다.
      스크린샷 2020-03-06 오후 6 56 29
      > Today Extension 과 Weather Framework 사용하여 날씨 위젯 만든 예시

      출처 | Building a Simple Widget for the Today View

      3 단계 원하는대로 Widget 만들기

      1. Taget 을 생성 시 자동 생성되는 파일

      기본적으로 스토리보드와 오토레이아웃을 지원하고 있으며 iOS 9.0 이상부터 사용이 가능하지만 대부분 iOS 9.0에서 deprecate 된 함수들이 많아서 iOS Target이 10.0부터 지원되는 앱에서 사용하길 추천한다.
      스크린샷 2020-03-06 오후 6 45 04

      • TodayViewController.swift
        • Contains the source code for the View Controller representing the widget in the Today view
      • MainInterface.storyboard
        • The storyboard file containing the user interface of the widget as it will appear within the Today view
      • Info.plist
        • The information property list for the extension.

      2. 템플릿 코드 살펴보기

      아래는 XCode에서 Today Extension Target 생성 시 자동으로 생성되는 템플릿 코드인데, 눈여겨 봐야할 것은 widgetPerformUpdate 메소드이다.

      import UIKit
      import NotificationCenter
      class TodayViewController: UIViewController, NCWidgetProviding {

      override func viewDidLoad() {
          super.viewDidLoad()
          // Do any additional setup after loading the view.
      }
      func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
          // Perform any setup necessary in order to update the view.
          // If an error is encountered, use NCUpdateResult.Failed
          // If there's no update required, use NCUpdateResult.NoData
          // If there's an update, use NCUpdateResult.NewData
          completionHandler(NCUpdateResult.newData)
      }
      

      }

      NCWidgetProviding 은 NotificationCenter 내부의 프로토콜인데
      스크린샷 2020-03-06 오후 7 31 26
      출처 | NotificationCenter Framework
      NCWidgetProviding 은 아래와 같은 메소드를 포함한다.
      스크린샷 2020-03-06 오후 7 28 18

      3 단계 원하는대로 Widget 만들기

      여기까지 하고 바로 시뮬레이터에서 동작시키면, 기본적으로 MainInterface.storyboard 에 셋팅되어있는 Hello World 라벨만 뜨게 될 것이다.
      스크린샷 2020-03-06 오후 8 24 52
      스크린샷 2020-03-06 오후 8 21 23
      이를 아무리 수정하려고 Storyboard 를 만져보아도 Storyboard UI 가 업데이트 되지 않아 난항을 겪었는데, 한번 따라해보자.

        1. 위젯의 사이즈를 width = 320, height = 110 으로 조절한다.


        Set the view to 110pts tall and 320pts wide in the Size Inspector. This is the default iPhone widget size.

        1. 앱과 위젯 간의 데이타를 update 하는 경우

      App extension의 번들이 containing app 번들에 포함되어 있는 구조이기는 하지만, 실행 중인 app extension과 containing app는 각자의 컨테이너에 직접적으로 접근할 수 없다. 사실상 두 앱이 작동되는 것이므로 둘 사이에 데이터를 교환할 필요가 있을 경우에는 App Groups를 사용하여 shared container를 통해 데이터 공유를 가능하게 한다. containing app의 프로젝트 설정 > Capabilities 탭

        1. 위젯이 “Unable to Load” 뜨는 경우

      디바이스나 Simulator 가 실행 중인 상태에서 Xcode > Debug > Attach to Process 들어가서 위젯 이름을 찾는다.

        1. Xib 생성

      처음에는 MainInterface.storyboard 에다가 아무리 UI 를 그려도 반영이 되지 않아 무엇이 문제인지 몰랐다.
      구글링을 해보니, Custom Widget 과 관련된 모든 포스팅에서 xib 를 생성해서 ViewController 에다가 register 시켜서 사용하는 것을 보고 혹시나 이게 문제일까 싶어 따라했더니 반영되었다.
      (정말 이게 문제가 맞는지 원인을 알고 싶다.)

      override func viewDidLoad() {
      super.viewDidLoad()

      // Allow the today widget to be expanded or contracted.
      extensionContext?.widgetLargestAvailableDisplayMode = .expanded
      // Register the table view cell.
      let ddayTableViewCellNib = UINib(nibName: "DdayTableViewCell", bundle: nil)
      tableView.register(ddayTableViewCellNib, forCellReuseIdentifier: DdayTableViewCell.reuseIdentifier)
      

      }

      확인 단계 디바이스에서 동작해보기

      스크린샷 2020-03-06 오후 9 56 21

    • 야곰
      키 마스터
      • 글작성 : 37
      • 답글작성 : 579

      깔끔한 정리 정말 인상깊습니다!
      질문이 몇가지 있습니다 🙂
      맨 아래쪽에 위젯의 사이즈를 320*110으로 조절했는데, 특별한 이유가 있을까요?
      위젯의 크기를 좀 더 다양하게 구성할 수도 있나요?
      위젯을 xib로 구성하지 않고도 해결할 수 있는 방법은 없나요?
      p.s. 야곰닷넷에서 코드블럭은 백틱(`) 세 대 대신 물결(~) 세 개를 쓰면 됩니다.

      • 뚭니
        참가자
        • 글작성 : 9
        • 답글작성 : 17

        긴 글 읽고 답글 남겨주셔서 감사합니다!

        1. 위젯의 사이즈를 320*110으로 조절했는데, 특별한 이유가 있을까요? 위젯의 크기를 좀 더 다양하게 구성할 수도 있나요?
          남겨주신 질문에 대해 제가 알고있는대로 답변드리자면 iOS 10이상 부터 compact 모드에서는 위젯의 높이가 110 으로 고정되고 overwrite 할 수 없다고 알고 있습니다. 만약 위젯의 크기를 다양하게 구성하고 싶다면 expanded 모드에서 위젯의 높이를 원하는대로 설정할 수 있습니다. 제가 테스트로 위젯을 만들어보았을 때는 이러한 이유도 있었지만 height를 110 으로 빌드하였을 때 정말 다른 앱의 위젯들과 compact 사이즈가 동일한지 확인해보고 싶었습니다. 그리고 실제 시뮬레이터에서 구동해보았을 때 높이가 동일한 것을 확인할 수 있었습니다.
          스크린샷 2020-03-16 오후 6 31 38
        2. 위젯을 xib로 구성하지 않고도 해결할 수 있는 방법은 없나요?
          본문에 적었던 것처럼 처음에는 Widget 의 storyboard 에 UI 를 그려 시도해보았었는데요,
          오토레이아웃도 적절하게 설정했다고 판단했습니다만 어떤 이유때문인지 UI 의 변경사항이 반영되지 않고 Hello World 라벨이 계속 나타났습니다. 그래서 다른 대안을 찾던 도중 xib 를 생성하여 register 하니 커스텀한대로 반영이 되었습니다.
          위젯을 xib가 아닌 방법으로 구성하고 싶다면 코드로 구성하는 대안이 있습니다. TodayExtension 의 Info.plist 에서 NSExtension 딕셔너리가 갖고 있는 NSExtensionMainStoryboard 키를 삭제하고 NSExtensionPrincipalClass 키를 추가하여 해당 ViewController 를 설정한다면 스토리보드 없이 위젯을 구성할 수 있습니다. 이때 반드시 loadView 메소드를 선언해야한다고 알고 있습니다.
          스크린샷 2020-03-16 오후 6 46 32

        혹시 제가 놓치고 있는 부분이 있어 짚어주신다면 무척 감사한 마음으로 공부하여 보충하겠습니다.
        질문해주셔서 감사합니다.

        • 야곰
          키 마스터
          • 글작성 : 37
          • 답글작성 : 579

          상세한 답변 고맙습니다.
          xib 말고 스토리보드로도 활용이 가능할텐데, 왜 되지 않았는지는 저도 확실히 모르겠네요 +_+
          오토레이아웃 문제였을 가능성이 가장 큰데, iOS 13부터 변경된 점이 있어서 그런지는 저도 확신이 없습니다.
          이전 버전까지는 스토리보드로도 충분했었어요.
          Xcode 오른쪽 위의 Identity Inspector에서 제대로 클래스가 맞게 설정됐는지, Target membership으로 제대로 등록되었는지 등을 한 번 다시 확인해 보는 것도 좋을 것 같아요.
          좀 우스운 말처럼 들릴 수도 있지만 영 안되면 클린빌드 + 재부팅도 종종 도움이 됩니다.

1 답변 글타래를 보이고 있습니다
  • 답변은 로그인 후 가능합니다.

logo landscape small

사업자번호 : 743-81-02195
통신판매업 신고번호 : 제 2022-충북청주-1278 호
고객센터 : 카카오톡채널 @yagom