let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
appearance.backgroundColor = .systemBlue
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
UINavigationBar.appearance().tintColor = .white
 

Status bar 색상 변경

1. Status Bar Style을 Light Content로 변경 2. Info.plist 설정 Key: View controller-based status bar appearance Value: No

betterhee.tistory.com

 

'' 카테고리의 다른 글

Handling Different Data Types in Core Data  (0) 2023.09.04
Javascript Interface (Bridge)  (0) 2022.11.24
딥링크 (URL Scheme, Universal Link)  (0) 2022.11.21
Status bar 색상 변경  (0) 2022.11.18
면접질문  (0) 2022.11.08

Transient Attribute

Transient Attribute(임시 속성)을 사용하여 비지속적 값 파생

  • 동일한 엔티티에 저장된 하나 이상의 속성에서 파생됨
  • 이름에서 알 수 있듯이 저장소에 유지되지 않
  • 저장 공간을 아낄 수 있다

예제 코드

// publishMonthID는 publishDate로부터 파생된 임시 속성

@objc public var publishDate: Date? {
    get {
        willAccessValue(forKey: Name.publishDate)
        defer { didAccessValue(forKey: Name.publishDate) }
        return primitivePublishDate
    }
    set {
        willChangeValue(forKey: Name.publishDate)
        defer { didChangeValue(forKey: Name.publishDate) }
        primitivePublishDate = newValue
        primitivePublishMonthID = nil // publishDate의 setter 메서드는 primitivePublishMonthID를 무효화합니다. 이를 통해 publishMonthID의 getter 메서드가 현재 publishDate를 기반으로 값을 다시 계산할 수 있습니다.
    }
}

@objc public var publishMonthID: String? {
    willAccessValue(forKey: Name.publishMonthID)
    defer { didAccessValue(forKey: Name.publishMonthID) }

    // publishMonthID의 getter 메서드가 현재 publishDate를 기반으로 값을 다시 계산
    guard primitivePublishMonthID == nil, let date = primitivePublishDate else {
        return primitivePublishMonthID
    }
    let calendar = Calendar(identifier: .gregorian)
    let components = calendar.dateComponents([.year, .month], from: date)
    if let year = components.year, let month = components.month {
        primitivePublishMonthID = "\(year * 1000 + month)"
    }
    return primitivePublishMonthID
}

// publishMonthID가 publishDate와 연결되고 항상 최신 상태를 유지할 수 있다.
// PublishMonthID가 Swift에서 Key-Value Observing을 사용하는 경우, 
// 다음 코드는 publishDate가 변경될 때 observation이 트리거되도록 보장
class func keyPathsForValuesAffectingPublishMonthID() -> Set<String> {
    return [Name.publishDate]
}

Derived Attribute

Derived Attribute(파생 속성)을 사용하여 다른 값에서 하나의 값 파생

  • 저장 공간보다 성능이 더 중요한 경우 사용
  • 사용자가 managed context를 저장할 때만 업데이트 됨

예제 코드

  • canonical: 함수는 대소문자와 발음 구별 부호를 구분하지 않는 문자열 값을 반환
  • 파생 속성은 사용자가 managed context를 저장할 때만 업데이트 되므로, title 속성을 저장하지 않고 변경하는 경우 canonicalTitle은 변경되지 않음

Predicate Programming Guide - String Comparisons
NSDerivedAttributeDescription | Apple Developer Documentation

extension ViewController: UISearchResultsUpdating {

    func updateSearchResults(for searchController: UISearchController) {
        let predicate: NSPredicate
        if let userInput = searchController.searchBar.text, !userInput.isEmpty {

            // Searching title with "diacritic insensitive" option gets the same result:
            //     predicate = NSPredicate(format: "title CONTAINS[cd] %@", userInput)
            // However, searching canonicalTitle avoids doing diacritic insensitive comparison every time,
            // and hence has better performance.
            //
            predicate = NSPredicate(format: "canonicalTitle CONTAINS[c] %@", userInput)
        } else {
            predicate = NSPredicate(value: true)
        }

        fetchedResultsController.fetchRequest.predicate = predicate
        do {
            try fetchedResultsController.performFetch()
        } catch {
            fatalError("###\(#function): Failed to performFetch: \(error)")
        }

        tableView.reloadData()
    }
}

Transformable Attribute

Transformable Attribute(변환 가능한 속성)을 사용하여 비표준 유형의 객체를 저장할 수 있음

예제 코드

  1. 타입을 Transformable로 설정하고, Data Model Inspector에서 Transformer 및 Custom Class 이름을 지정하여 구성
  2. 앱이 Core Data Stack을 로드하기 전에 코드로 Transformer을 등록
lazy var persistentContainer: NSPersistentContainer = {
     // Register the transformer at the very beginning.
     // .colorToDataTransformer is a name defined with an NSValueTransformerName extension.
     ValueTransformer.setValueTransformer(ColorToDataTransformer(), forName: .colorToDataTransformer)

     let container = NSPersistentContainer(name: "CoreDataAttributes")
     container.loadPersistentStores(completionHandler: { (_, error) in
         guard let error = error as NSError? else { return }
         fatalError("###\(#function): Failed to load persistent stores:\(error)")
     })

     container.viewContext.automaticallyMergesChangesFromParent = true
     SampleData.generateSampleDataIfNeeded(context: container.newBackgroundContext())

     return container
}()

Date Type

Decimal Type

let value = UInt64(arc4random_uniform(9999))
book.price = NSDecimalNumber(mantissa: value, exponent: -2, isNegative: false)

cell.price.text = book.price?.description(withLocale: Locale.current) // ex) 98.14

'' 카테고리의 다른 글

Navigation bar 색상변경  (0) 2023.09.20
Javascript Interface (Bridge)  (0) 2022.11.24
딥링크 (URL Scheme, Universal Link)  (0) 2022.11.21
Status bar 색상 변경  (0) 2022.11.18
면접질문  (0) 2022.11.08

1. 플러그인 설치

$ flutter pub add url_launcher
$ flutter pub add webview_flutter

2. 플랫폼별 설정

외부 앱을 실행하려면 플랫폼별로 해당하는 파일에 앱 실행 허용 목록을 설정해야 합니다.
또한 외부 앱 실행 후 다시 기존 서비스 앱으로 돌아오고싶다면 서비스하는 앱 스킴 또한 등록해주어야 합니다.

kakao developers | 커스텀 URL 스킴 설정 방법 참고
url_launcher | Flutter Package

Android 설정 (AndroidManifest.xml)

<!-- Provide required visibility configuration for API level 30 and above -->
<queries>
  <!-- If your app checks for SMS support -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="sms" />
  </intent>
  <!-- If your app checks for call support -->
  <intent>
    <action android:name="android.intent.action.VIEW" />
    <data android:scheme="tel" />
  </intent>
</queries>

iOS 설정 (info.plist)

<key>LSApplicationQueriesSchemes</key>
<array>
  <string>sms</string>
  <string>tel</string>
</array>

3. 웹뷰에서 외부 앱 실행

// WebViewController
WebViewController()
      ..setJavaScriptMode(JavaScriptMode.unrestricted)
      ..setNavigationDelegate(
        NavigationDelegate(
          onProgress: (int progress) {
            debugPrint('WebView is loading (progress : $progress%)');
          },
          onPageStarted: (String url) {
            debugPrint('Page started loading: $url');
          },
          onPageFinished: (String url) {
            debugPrint('Page finished loading: $url');
          },
          onWebResourceError: (error) {
            debugPrint('''
Page resource error:
  code: ${error.errorCode}
  description: ${error.description}
  errorType: ${error.errorType}
  isForMainFrame: ${error.isForMainFrame}
          ''');
          },
          onNavigationRequest: (request) {
            if (request.url.startsWith('http:') ||
                request.url.startsWith('https:')) {
              debugPrint('allowing navigation to ${request.url}');
              return NavigationDecision.navigate;
            }
            debugPrint('blocking navigation to ${request.url}');
            _launchUrl(request.url);
            return NavigationDecision.prevent;
          },
          onUrlChange: (UrlChange change) {
            debugPrint('url change to ${change.url}');
          },
        ),
      )

...

Future<void> _launchUrl(String url) async {
  if (!await launchUrl(
    Uri.parse(url),
    // mode: LaunchMode.externalApplication,
  )) {
    throw Exception('Could not launch $url');
  }
}
open ~/Library/flutter/packages/flutter_tools/gradle/flutter.gradle

 

 

/** For apps only. Provides the flutter extension used in app/build.gradle. */
class FlutterExtension {
    /** Sets the compileSdkVersion used by default in Flutter app projects. */
    static int compileSdkVersion = 33

    /** Sets the minSdkVersion used by default in Flutter app projects. */
    static int minSdkVersion = 16

    /** Sets the targetSdkVersion used by default in Flutter app projects. */
    static int targetSdkVersion = 33
    
    // ...
}

 

'Flutter' 카테고리의 다른 글

웹뷰에서 외부 앱 실행하는 방법 (URL Scheme)  (0) 2023.07.20

Native → Javascript 호출

[방법 1] 미리 등록한 시점에 자바스크립트 실행

let javascriptSourceCode = "MyFunction" // 실행할 자바스크립트

let userScript = WKUserScript(
	source: javascriptSourceCode,
    injectionTime: .atDocumentStart, // 스크립트를 웹페이지에 삽입하는 시간을 결정
//    injectionTime: .atDocumentEnd,  
    forMainFrameOnly: true
)

let userContentController = WKUserContentController()
userContentController.addUserScript(userScript)

[방법 2] 필요한 시점에 자바스크립트 실행

let javascriptSourceCode = "MyFunction" // 실행할 자바스크립트

webView.evaluateJavaScript(javascriptSourceCode) { result, error in
    if let error = error {
        print(error.localizedDescription)
    } else if let result = result {
        print(result)
    }
}

Javascript → Native 수신

  • Javascript
  • window.webkit.messageHandlers.<name>.postMessage(<messageBody>) // ex) window.webkit.messageHandlers.MyFunction.postMessage()

1. Message Handler 등록

class ViewController: UIViewController {

    let messageHandlerName: String = "MyFunction"

    func configureWebView() {
        let userContentController = WKUserContentController()
        userContentController.add(self, name: messageHandlerName)
        
        let configuration = WKWebViewConfiguration()
        configuration.userContentController = userContentController
        webView = WKWebView(frame: frame, configuration: configuration)
    }

}

2. Message Hanlder 처리

extension ViewController: WKScriptMessageHandler {

    func userContentController(
        _ userContentController: WKUserContentController,
        didReceive message: WKScriptMessage
    ) {
        if message.name == messageHandlerName {
            // 처리할 내용
            print(message.body)
        }
    }
}

 

'' 카테고리의 다른 글

Navigation bar 색상변경  (0) 2023.09.20
Handling Different Data Types in Core Data  (0) 2023.09.04
딥링크 (URL Scheme, Universal Link)  (0) 2022.11.21
Status bar 색상 변경  (0) 2022.11.18
면접질문  (0) 2022.11.08

딥링크는 앱이 실행되거나 앱 내 특정 화면으로 이동시키는 기능을 수행하는 링크입니다.


딥링크는 2가지 방식으로 구분됩니다.

  • URL Scheme 방식 : 앱에 URL Sscheme 값을 등록하는 방식
  • Universal Link 방식 : 도메인 주소를 이용하는 방식

 

While custom URL schemes are an acceptable form of deep linking, universal links are strongly recommended. For more information on universal links, see Allowing apps and websites to link to your content.

Apple 공식문서에서는 custom URL Scheme 방식보다는 Universal Link 방식을 강력히 권장합니다.

URI 스킴 방식

클라이언트 앱서버 앱을 호출한다고 가정합니다.

1. 서버 앱URL 형식을 정의합니다.

// ex) 사진 라이브러리 앱의 경우 표시할 앨범의 name이나 index를 포함하는 URL 형식을 정의합니다. 
myphotoapp:albumname?name="albumname"
myphotoapp:albumname?index=1

2. 서버 앱에서 URL 스키마를 등록합니다.

URL 스키마 등록

  • [Target] - [Info] - [URL Types]
  • identifier은 고유성을 보장하기 위해 (도메인+앱 이름)을 역방향으로 작성한 DNS 문자열을 사용하는 것을 권장합니다.

3. 클라이언트 앱에서 서버 앱URL을 호출합니다.

let url = URL(string: "myphotoapp:Vacation?index=1")

UIApplication.shared.open(url!) { (result) in
    if result {
       // The URL was delivered successfully!
    }
}

4. 서버 앱에서 수신한 URL을 처리합니다.

// Scene-based App
func scene(_ scene: UIScene, 
           willConnectTo session: UISceneSession, 
           options connectionOptions: UIScene.ConnectionOptions) {

    // Determine who sent the URL.
    if let urlContext = connectionOptions.urlContexts.first {

        let sendingAppID = urlContext.options.sourceApplication
        let url = urlContext.url
        print("source application = \(sendingAppID ?? "Unknown")")
        print("url = \(url)")

        // Process the URL.
        guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
            let albumPath = components.path,
            let params = components.queryItems else {
                print("Invalid URL or album path missing")
                return false
        }

        if let photoIndex = params.first(where: { $0.name == "index" })?.value {
            print("albumPath = \(albumPath)")
            print("photoIndex = \(photoIndex)")
            return true
        } else {
            print("Photo index missing")
            return false
        }
    }
}
  • 앱이 메모리에 실행되고 있지 않은 경우, 시스템은 앱을 실행한 후 scene(_:willConnectTo:options:) 메서드를 호출하여 URL을 전달합니다.
  • 메모리에서 실행 중이거나 정지된 상태일 경우, scene(_:openURLContexts:) 메서드에 URL를 호출하여 전달합니다.

유니버셜 링크 방식

유니버셜 링크를 이용하면 앱이 설치되어 있는 경우 앱으로 이동하고, 없으면 앱을 설치할 수 있는 앱스토어로 이동합니다.

1. 서버 앱에 유니버셜 링크를 지원하기 위해 도메인을 추가합니다.

2. 클라이언트 앱에서 서버 앱유니버셜 링크를 호출합니다.

if let appURL = URL(string: "https://myphotoapp.example.com/albums?albumname=vacation&index=1") {
    UIApplication.shared.open(appURL) { success in
        if success {
            print("The URL was delivered successfully.")
        } else {
            print("The URL failed to open.")
        }
    }
} else {
    print("Invalid URL specified.")
}

3. 서버 앱에서 수신한 유니버셜 링크를 처리합니다.

func scene(_ scene: UIScene, willConnectTo
           session: UISceneSession,
           options connectionOptions: UIScene.ConnectionOptions) {

    // Get URL components from the incoming user activity.
    guard let userActivity = connectionOptions.userActivities.first,
        userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let incomingURL = userActivity.webpageURL,
        let components = NSURLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
        return
    }

    // Check for specific URL components that you need.
    guard let path = components.path,
        let params = components.queryItems else {
        return
    }
    print("path = \(path)")

    if let albumName = params.first(where: { $0.name == "albumname" })?.value,
        let photoIndex = params.first(where: { $0.name == "index" })?.value {

        print("album = \(albumName)")
        print("photoIndex = \(photoIndex)")
    } else {
        print("Either album name or photo index missing")
    }
}

 

참조

'' 카테고리의 다른 글

Handling Different Data Types in Core Data  (0) 2023.09.04
Javascript Interface (Bridge)  (0) 2022.11.24
Status bar 색상 변경  (0) 2022.11.18
면접질문  (0) 2022.11.08
Sync/Async  (0) 2022.11.05

 

Status Bar 콘텐츠 색상 변경

 

1. Status Bar Style을 Light Content로 변경

 

2. Info.plist 설정
Key: View controller-based status bar appearance 
Value: No

 

 

'' 카테고리의 다른 글

Javascript Interface (Bridge)  (0) 2022.11.24
딥링크 (URL Scheme, Universal Link)  (0) 2022.11.21
면접질문  (0) 2022.11.08
Sync/Async  (0) 2022.11.05
GCD/Operation  (0) 2022.11.05

+ Recent posts