• Focus: 특정 시간동안 시스템의 행동을 조정해서 원하는 것에 집중할 수 있도록 하는 기능

    • 노티피케이션 동작에 크게 영향을 줌
  • Focus filter: 현재 포커스 상태에 따라 앱의 동작을 커스터마이징 할 수 있는 기능

    • 시스템 달력 앱에서, 포커스 여부에 따라 보여주는 일정이 달라진다. → 포커스로 인해 필터링 되었다는 것을 알려준다.
    • 메일 앱에서도 노티피케이션이 포커스에 달라지기 때문에, 앱도 기능이 달라지는게 자연스럽다.
  • Use case

    • 계정이 여러개고, 이 계정이 각각 기능이 다르다.
    • 포커스 상태에 따라 특정 데이터를 필터링한다.
    • 유저의 주의가 분산되는 것을 막기 위해서 뱃지 카운트를 줄이거나, 인앱 얼럿이나 노티피케이션을 줄인다.
    • 포커스 상태에 따라서 앱의 외관을 바꾼다.
  • 동작 원리

    • App: AppIntent를 통해서 커스터마이징이 가능한 부분을 정의한다.
    • System: App에서 정의한 것을 기반으로 사용자가 커스터마이징할 수 있는 요소를 Focus 세팅에서 노출한다.
    • User: Focus 설정에서 실제로 설정을 한다.
  • Defining a Focus filter

    • SetFocusFilterIntent 구현

      • title과 description은 정적인 텍스트고, 앱이 설치될 때 설정된다.
      // Implementing SetFocusFilterIntent
      
      import AppIntents
      
      struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
      
          static var title: LocalizedStringResource = "Set account, status & look"
          static var description: LocalizedStringResource? = """
              Select an account, set your status, and configure
              the look of Example Chat App.
          """
      }
      

    스크린샷 2022-06-11 오후 11.54.42.png

    스크린샷 2022-06-11 오후 11.55.01.png

    • 매개변수 정의: 실제로 앱에서 설정 가능한 부분을 정의

      • @Parameter 프로퍼티 래퍼로 데코레이팅 된 프로퍼티들

      • 커스텀 타입을 스는 경우는 Enitity로 정의되어야 한다. → AppIntent 프레임워크 참조

      • 옵셔널 타입인 경우는 선택적으로 제공하며, 논옵셔널인 경우는 반드시 기본 값을 제공해야 한다.

        // Defining your Parameters & Entities
        
        import AppIntents
        
        struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
        
            @Parameter(title: "Use Dark Mode", default: false)
            var alwaysUseDarkMode: Bool
        
            @Parameter(title: "Status Message")
            var status: String?
        
            @Parameter(title: "Selected Account")
            var account: AccountEntity?
        
            // ...
        }
        
    • display representation 설정: focus filter 설정이 이미 되어 있는 경우의 표현

      • 이 경우는 사용자 설정 값을 기반으로 동적으로 만들어낼 수 있다.

        // Display Representation
        
        struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
            // ...
          
            var localizedDarkModeString: String {
                return self.alwaysUseDarkMode ? "Dark" : "Dynamic"
            }
        
            var displayRepresentation: DisplayRepresentation {
                var titleList: [LocalizedStringResource] = [], subtitleList: [String] = []
                if let account = self.account {
                    titleList.append("Account")
                    subtitleList.append(account.displayName)
                }
                if let status = self.status {
                    titleList.append("Status")
                    subtitleList.append(status)
                }
                titleList.append("Look")
                subtitleList.append(self.localizedDarkModeString)
            
                let title = LocalizedStringResource("Set \\(titleList, format: .list(type: .and))")
                let subtitle = LocalizedStringResource("\\(subtitleList.formatted())")
        
                return DisplayRepresentation(title: title, subtitle: subtitle)
            }
          
            // ...
        }
        
  • Acting on a Focus filter

    • Focus filter가 변경된 경우 AppIntent를 통해서 앱에 전달된다.

      • 앱이 동작 중인 경우는 FocusFilterIntent의 메소드가 구현되어 있다면 호출한다. → 뷰만 업데이트 할거면 extension은 필요없을 것이다.
      • 앱이 동작 중이지 않으면 App Extension을 통해서 FocusFilterIntent의 메소드를 호출한다. → 위젯, 노티피케이션, 뱃지 등을 대응해야 한다면 Extension을 구현해야 할 것 이다.
    • FocusFilterIntent의 perform 메소드를 구현한다.

      import AppIntents
      
      struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
          // ...
      
          func perform() async throws -> some IntentResult {
              let myData = AppData(
                  alwaysUseDarkMode: self.alwaysUseDarkMode,
                  status: self.status,
                  account: self.account
              )
              myModel.shared.updateAppWithData(myData)
              return .result()
          }
        
          // ...
      }
      
    • 현재 포커스 필터 값을 쿼리해오기

      // Calling Current
      
      import AppIntents
      
      func updateCurrentFilter() async throws {
          do {
              let currentFilter = try await ExampleChatAppFocusFilter.current
              let myData = AppData(
                  myRequiredBoolValue: currentFilter.myRequiredBoolValue,
                  myOptionalStringValue: currentFilter.myOptionalStringValue,
                  myOptionalAppEnum: currentFilter.myOptionalAppEnum,
                  myAppEntity: currentFilter.myAppEntity
              )
              myModel.shared.updateAppWithData(myData)
          } catch let error {
              print("Error loading current filter: \\(error.localizedDescription)")
              throw error
          }
      }
      
  • Providing additional context

    • Focus Filter를 통해서 바뀐 앱의 동작을 시스템이 알 수 있게 하는 것 → 그렇게 다양한 케이스를 제공하지는 않는다.
    • 유즈 케이스
      • 노티피케이션 필터링
      • 뱃지 카운트 설정
    • 이를 위해서는 AppContext 프로퍼티를 제공해야 한다.
      • perform 메소드의 결과값으로
      • invalidateFocusFilterAppContext() 메소드를 명시적으로 호출함으로
  • 노티피케이션 필터링 예제

    // Set filterPredicate on an App context
    
    import AppIntents
    
    struct ExampleChatAppFocusFilter: SetFocusFilterIntent {
    
        var appContext: FocusFilterAppContext {
            let allowedAccountList = [account.identifier]
            let predicate = NSPredicate(format: "SELF IN %@", allowedAccountList)
            return FocusFilterAppContext(notificationFilterPredicate: predicate)
        }
    }
    
    // Pass filterCriteria on UNNotificationContent
    // remote notification 페이로드에도 담을 수 있다.
    let content = UNMutableNotificationContent()
    content.title = "Curt Rothert"
    content.subtitle = "Slide Feedback"
    content.body = "The run through today was great. I had few comments about slide 22 and 28."
    content.filterCriteria = "work-account-identifier"
    
  • 뱃지 카운트 조정

    • UNUserNotificationCenter의 setBadgeCount 메소드를 호출해주면 된다.
  • 이러한 기능을 제공하는 목적은 유저가 focus 모드를 설정했을 때 관련한 정보만 보여주기 위함이다.