[iOS] 런타임에 언어 바꾸기

iOS 프로젝트에서 Localizable.string 파일을 만들어 앱의 다양한 언어를 지원할 수 있다. 기본적으로 유저가 사용하고 있는 OS 세팅에 적용되어 있는 언어를 앱은 받아드리고 보여주게 될 것이다. 하지만 필자 같이 영문 OS 버전을 사용하면서 특정 앱에 한에서 한국어를 사용하고 싶을 때가 있다. 하지만 CocoaTouch에 선언되어 있는 NSLocalizedString 매크로를 통해서 앱의 언어를 바꾸려면 앱을 메모리에서 한번 지우고 다시 앱을 열어야지만 새 언어를 적용할 수 있다는 특징이 있다. 꽨 번거로운 일이다. 그래서 필자는 런타임에 언어를 바꿀수 있는 방법을 찾아보다가 괜찮은 방법을 찾았기에 소개하고자 한다.

본 글은 stackoverflow에서 찾은 솔루션이다.

링크

기본적인 아이디어는 애플에서 적용되어 있는 NSLocalizedString 매크로를 재정의 하여 사용하는 것이다.

  1. NSBundle의 카테고리를 만든다.
  2. AppDelegate에 language 코드를 알려주는 인스턴스 메서드를 만든다.
  3. NSLocalizedString 매크로를 사용하는 파일에 카테코리 파일을 임포트 한다.

NSBundle의 카테고리 만들기

  1. //
  2. //  NSBundle+RunTimeLanguage.h
  3. //  Copyright © 2016 bartysways. All rights reserved.
  4. //
  5.  
  6. @import Foundation;
  7.  
  8. #undef NSLocalizedString
  9. #define NSLocalizedString(key, comment) [[NSBundle mainBundle] runTimeLocalizedStringForKey:(key) value:@"" table:nil]
  10.  
  11. @interface NSBundle (RunTimeLanguage)
  12.  
  13. - (NSString *)runTimeLocalizedStringForKey:(NSString *)key
  14.                                      value:(NSString *)value
  15.                                      table:(NSString *)tableName;
  16.  
  17. @end
  18.  
  19. //
  20. //  NSBundle+RunTimeLanguage.m
  21. //  Copyright © 2016 bartysways. All rights reserved.
  22. //
  23.  
  24. #import "NSBundle+RunTimeLanguage.h"
  25. #import "AppDelegate.h"
  26.  
  27. @implementation NSBundle (RunTimeLanguage)
  28.  
  29. - (NSString *)runTimeLocalizedStringForKey:(NSString *)key
  30.                                      value:(NSString *)value
  31.                                      table:(NSString *)tableName
  32. {
  33.     AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
  34.     NSString *path = [[NSBundle mainBundle] pathForResource:appDelegate.languageCode
  35.                                                      ofType:@"lproj"];
  36.     NSBundle *languageBundle = [NSBundle bundleWithPath:path];
  37.     NSString *localizedString= [languageBundle localizedStringForKey:key value:key table:nil];
  38.  
  39.     return localizedString;
  40. }
  41.  
  42. @end

AppDelegate에 인스턴스 메서드 만들기

  1. //
  2. //  AppDelegate.h
  3. //  Copyright © 2015 bartysways. All rights reserved.
  4. //
  5.  
  6. @import UIKit;
  7.  
  8. @interface AppDelegate : UIResponder
  9. <
  10. UIApplicationDelegate
  11. >
  12.  
  13. @property(nonatomic, strong) UIWindow *window;
  14. @property(nonatomic, strong, readonly) NSString *languageCode;
  15.  
  16. @end
  17.  
  18. //
  19. //  AppDelegate.m
  20. //  Copyright © 2015 bartysways. All rights reserved.
  21. //
  22.  
  23. @import UserNotifications;
  24.  
  25. #import "AppDelegate.h"
  26.  
  27. @implementation AppDelegate
  28.  
  29. - (NSString *)languageCode
  30. {
  31.     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  32.     NSString *languageCode = ((NSArray *)[userDefaults objectForKey:@"AppleLanguages"]).firstObject;
  33.     return languageCode != nil ? languageCode : @"en";
  34. }
  35. @end

카테코리 파일을 임포트하기

  1. //
  2. //  HeaderCollectionReusableView.m
  3. //  Copyright © 2016 bartysways. All rights reserved.
  4. //
  5.  
  6. #import "HeaderCollectionReusableView.h"
  7. #import "NSBundle+RunTimeLanguage.h"
  8.  
  9. @implementation HeaderCollectionReusableView
  10. @end

[Multi-Thread 프로그래밍] Swift에서 GCD 사용하기

몇해 전 Apple은 NSOperation과 동시에 멀티스레드 환경에서 작업할 수 있게 해주는 GCD 매카니즘을 시장에 내놓았다. iOS라는 기존 컴퓨터와 다른 특별한 환경에서 요구되는 빠른 반응성을 위해 무거운 과업을 Main Thread Queue가 아닌 Background thread Queue에서 실행해야만 한다. 이때 NSOperation 기술보다는 블록을 사용한 GCD가 더 가독성 높은 코드를 만들어 낼 수있기 때문에 많은 개발자의 사랑을 받고 있는 것 같다.

해당 블로그 포스트는 기본적인 멀티 스레드에 대한 개념과 GCD 라이브러리들에 대한 내용을 담을 것이다. 또한 본 포스트는 self-study에 대한 복습 정도의 메모를 주목적으로 하고 있기 때문에 자세한 설명이 없다면 댓글를 통해 문의 바란다.

멀티 스레딩의 기본 개념

  • 순차적(Serial) VS 동시적(Concurrent)

한 과업(Task)이 여러 과업들 중에 한번에 한개씩 실행된다면 ‘순차적(Serially)’ 진행이라 하고, 동시에 여러 과업이 동시에 실행이 된다면 ‘동시적(concurrently)’ 진행이다.

  • 과업 (Task)

이 글에서 설명하는 과업이란 Micro하게 보자면 오브젝티스-C에서는 ‘Block’으로 표현할 수 있고, Swift로 말하자면 ‘Closure’가 될 것이다. 블럭은 하나의 과업을 구현하고자 하는 코드 묶음이라고 할 수 있겠다. 그런데 이 블럭이나 클러져는 메서드의 파라미터나 인자, 혹은 클래스 인스턴스 Property가 되어 이곳 저곳으로 모듈화하여 사용되어 질 수 있다.

  • 동기식(Synchronous) VS 비동기식(Asynchronous)

동기식과 비동기식은 기본적으로 언제 과업을 호출한 곳에서 다음 코드 진행에 관한 컨트롤 권한을 넘겨 줄 것인가에 대한 개념이다. 예를 동기식은 자신의 과업 (여기에서 클러져나 블럭으로 표현되어 질 수 있다.)이 다 끝나고 코드 진행을 시키기 위해 컨트롤 권한을 준다면 ‘동기식’이고, 자신의 과업이 혹은 블럭이 다 완료 되지 않은 상황 가운데, 다음 코드로 넘어가게 한다면 비동기식일 것이다.

  • 임계구역 (Critical Section)

이 개념은 반드시 동시에 실행되어서는 안될 코드 구역을 말하는 것이다. 멀티 스레드 프로그래밍에서 공유자원을 사용할 때 여러 스레드에서 동시에 접근할 수 있는 위험성 있는 지역을 보호하는 개념이다.

  • 경합 조건(Race Condition)

멀티 스레드 프로그래밍에서 두 명령어가 동시에 같은 기억 장소를 접근하고자 할때 그들 사이의 경쟁에 의해 수행 결과를 예측할 수 없게 되는 것인데, 이와 같은 현상은 바람직하지 않으므로 OS에서 이것을 해소해주어야 할 것이다. 출처

  • 교착상태 (Deadlock)

한개 이상의 스레드에서 서로가 필요한 작업이나 리소스를 기다리는 상태이다. 예를 들어 첫번째 스레드가 두번째 스레드가 끝나야 일을 진행 할 수 있고, 두번째 스레드는 첫번째 스레드가 끝나야 일을 진행 할 수 있는 경우이다. 이런 경우에는 이도저도 못하는 교착상태에 빠져 버린다.

  • 스레드 세이프 (Thread Safe)

스레드 세이프한 코드는 멀티 스레드나 동시 과업 환경에서 아무런 문제 없이 호출 되어지고 실행가능한 코드를 말한다. Swift 언어의 let으로 시작하는 변수가 ‘스레드 세이프’하다고 할 수 있겠다. 초기화 할때 정해진 값이 끝까지 변하지 않기 때문이다. 반대로 var로 시작하는 변수는 ‘스레드 세이프’하지 않다. 멀티 스레드 환경에서 언제든지 읽혀지고 쓰여질 수 있기 때문이다.

  • 문맥 교환 (Context Switch)

한 프로세스 안에서 하나 이상의 스레드에서 다른 스레드로 작업환경을 변경하고자 할 때 저장과 불러오기의 과정을 문맥 교환이라고 한다. 문맥 교환이 잦아지면 성능의 문제가 있다. 최소화하는 것도 성능개선에 중요한 부분이다.

  • 동시성(Concurrency) VS 병렬성(Parallelism)

동시성은 두 개 이상의  과업이 시작, 실행, 완료등이 같은 시간대에서 행하여지는 것을 뜻한다. 그런데 주의 할 것은 두 과업이 반드시 같은 순간에 실행되는 것을 의미하지 않는다. 예를 들어, 싱글 코어 CPU에서 시간을 쪼개어 동시에 작업 중인 과업을 왔다갔다 하면서 연산한다. 반면 병렬성은 말그대로 동시에 과업을 수행하는 것을 뜻한다. 멀티 코어 CPU에서 가능하다.

출처. raywenderlich.com

출처. raywenderlich.com

Queue

GCD에서는 스레드 풀은 크게 두 가지 타입이 있는데, 순차적 큐와 동시적 큐이다. GCD를 사용하게 되면 스레드를 직접 생성 혹은 호출하여 사용하지 않는다. 스레드 풀인 큐에서 넣어 놓고 과업을 실행하게 되는데, 자신의 상황에 맞게 사용하기 위해서는 어떤 것들이 있는지 알아야 할 것이다.

  1. 순차적 큐(Serial Queue): 한번에 하나의 과업이 순차적으로 실행되어지는 스레드 큐를 뜻한다.  과업이 실행되는 순서는 FIFO(First In First Out)이다. GCD에서 dispatch_get_main_queue가 대표적인 순차적 큐이다. UI 작업은 메인 큐에서만 해야한다. 하지만 사용자 정의 순차적 큐도 만들 수 있다. 큐를 생성할 때는dispatch_queue_create(“com.bartysways.Project”, DISPATCH_QUEUE_SERIAL)와 같이 ‘DISPATCH_QUEUE_SERIAL’ 파라미터를 지정해주면 된다.
  2. 동시적 큐(Concurrent Queue): 동시적 큐는 비동기적인 작업을 할 때 사용하기에 알맞은 큐이다. 과업의 시작 순서는 전적으로 GCD가 판단을 하게 되어지는데, 어떤 코어에서 어떤 것을 먼저 실행하고 완료되어질지는 GCD와 과업의 종류에 따라 달렸다. dispatch_queue_create(“com.bartysways.Project”, DISPATCH_QUEUE_CONCURRENT).
출처: raywenderlich.com

출처: raywenderlich.com

Quality of Service

GCD에서 제공하는 QoS 클래스가 있다. 이 클래스는 GCD가 동시적 큐에서 과업의 우선순위를 정할 때 고려되어지는 클래스로써 과업의 의도를 추상화 하여 표현하여 네이밍을 하였다. QoS를 사용하여 GCD를 사용할 때의 장점은 각 디바이스 아키텍쳐에 맞는 환경을 시스템이 고려하여 멀티 스레딩을 효율적으로 해준다는 것에 있다. 물론 일일히 NSThread를 사용하여 관리 하여 코딩할 수 있지만, 많은 코딩과 많은 복잡도를 애플이 제공해주는 메카니즘에게 맡기고 그 혜택을 누릴 수 있는 것이다.

  • QOS_CLASS_USER_INTERACTIVE: 해당 클래스는 UI 업데이트를 위한 과업을 실행할 때 명시하는 클래스이다. 이 클래스를 사용하게 되면 과업은 순차적으로(동기식) 항상 Main Queue에서 실행된다.
  • QOS_CLASS_USER_INITIATED: 해당 클래스는 UI 이벤트를 통해 초기화 작업을 해야 할 때를 위해서 제공된다. 관련된 과업은 비동기식으로 실행된다. 예를 들어 사진 필터를 제공하는 기능이 있다고 가정을 하자, 원하는 필터 효과를 적용하기 위해서 버튼을 탭하고 해당 이미지를 필터에 맞는 효과를 적용할 때 필요한 과업을 해당 클래스를 지정하여 실행하면 효과적이다.
  • QOS_CLASS_UTILITY: 파일 I/O 작업이나 네트워킹 작업 같이 긴 시간을 필요로 하는 작업에 적당한 클래스이다.
  • QOS_CLASS_BACKGROUND: 이 클래스는 시간에 구애 받지 않고 사용자가 신경쓰지 않아도 되는 작업들을 백그라운드에서 실행하고자 할때 사용 되어지도록 디자인된 클래스이다. 예를 들어 로그아웃 시 로컬에 저장되어 있는 파일을 지워야 한다든지 혹은 이지미 파일을 다운로드 받은 후 캐싱을 한다든지 와 같은 작업을 할 때 적당할 것이다.

 

Practical Example

dispatch_async

  1. override func viewDidLoad(){
  2.    let queue = dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
  3.    dispatch_async(queue){
  4.        println("First Block")
  5.    }
  6.    println("Second Block")
  7. }

위 코드의 실행 결과는 ‘Second Block’이 먼저 실행된 다음 ‘First Block’이 실행된다. 글로벌 큐에서 dispatch_async를 하게 되면, 클로져 안에 들어 있는 코드는 메인 큐가 아닌 글로벌 큐에 들어가서 자신의 순서가 되어 있을 때 실행되어진다. 여기에서 말하는 글로벌 큐는 메인 큐가 메인 스레드를 뜻한다면, 새로운 스레드를 뜻하는데 소위 백그라운드 스레드라고 해도 무관할 것이다. (참고로 글로벌 큐는 해당 디바이스의 CPU 코어 갯수에 따라 갯수가 정해진다.)

dispatch_sync

  1. override func viewDidLoad(){
  2.     super.viewDidLoad()
  3.  
  4.    let queue = dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)
  5.    dispatch_sync(queue){
  6.        println("First Block")
  7.    }
  8.    println("Second Block")
  9. }

위 코드의 실행 결과는 ‘First Block’, ‘Second Block’ 순으로 실행이 된다. 물론 메인 큐가 아닌 글로벌 큐에서 작업이 실행 되지만, 해당 클로저의 코드가 다 끝나기 전까지 메인 스레드에게 통제권을 넘기지 않는다.

dispatch_after

  1. func executeDelay() {
  2.  
  3.     let delayInSeconds = 3.0
  4.     let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
  5.     let queue = dispatch_get_main_queue()
  6.  
  7.     dispatch_after(popTime, queue) {
  8.         println("Bang! Bang! Bang!")
  9.     }
  10.   }

위 코드는 3초 후에 클로져의 코드가 실행된다.

dispatch_once

  1. private var signalOnceToken = dispatch_once_t()
  2.  
  3.   override func viewDidLoad() {
  4.     super.viewDidLoad()
  5.  
  6.     dispatch_once(&signalOnceToken) {
  7.         println("Just one time!")
  8.     }
  9. }

dispatch_once는 프로세스가 떠 있는 동안 한번만 실행했으면 할 때 사용을 한다. 싱글톤 패턴을 구사 할 때 사용해도 무관하다.

Readers and Writers 문제 핸들링

dispatch_barrier_async

GCD의 barriers API를 ‘동시적큐’에서 사용하면 관련 과업은 타과업과 동시에 실행되지 않고 단독 독점을 하여 실행하게 된다. 이 API를 사용하여 읽기 작업이나 쓰기 작업을 한다면 두 개 이상의 Task에서 동시 접근을 막을 수 있게 된다.

  1. private let concurrentPhotoQueue = dispatch_queue_create(
  2.     "com.raywenderlich.GooglyPuff.photoQueue", DISPATCH_QUEUE_CONCURRENT)
  3.  
  4. func addPhoto(photo: Photo) {
  5.  
  6.   dispatch_barrier_async(concurrentPhotoQueue) {
  7.  
  8.     self._photos.append(photo)
  9.  
  10.     dispatch_async(GlobalMainQueue) {
  11.       self.postContentAddedNotification()
  12.     }
  13.   }
  14. }

위 예제에서 ‘_photos’ Array 개체에 새로운 값을 넣어 줄 때, dispatch_barrier_async 함수를 사용한다면 스레드 안전하게 실행할 수 있게 된다.

Screen Shot 2015-07-16 at 4.47.45 PM

위 그림을 살펴보자. 동시적큐에서 여러 과업이 병렬되어 실행되고 있는 모습을 볼 수 있다. 그런데 ‘Barrier Task’ 블럭을 보면 단독적으로 시간을 보내는 모습을 볼 수 있다.

Dispatch Apply && Dispatch Groups

Dispatch group은 그룹의 묶음으로 지정된 과업이 다 마쳤을 때 notify해주는 기능을 가지고 있다. 실예로 iOS 프로젝트에서 UITableView를 사용할 때 화면에 보이는 각 cell마다 thumbnail 이미지를 다운로드 과업들이 다 끝났을 때 알림을 받고 싶다면 해당 API를 써서 구현할 수 있다. 아래 예제를 보고 확인해보자.

  • dispatch_apply: for 반복문 처럼 동작한다.
  • dispatch_group_create:
  • dispatch_group_enter
  • dispatch_group_leave
  • dispatch_group_wait
  • dispatch_group_notify: 비동기식으로 그룹이 들어온 만큼 다 나가면 디스패치 된다.

  1. func downloadWithCompletion(completion: DownloadingCompletionClosure?) {
  2.  
  3.     var storedError: NSError?
  4.     var downloadGroup = dispatch_group_create()
  5.     let addresses = ["http://www.....", "http://www.....", "http://www....."]
  6.     let queue = dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
  7.  
  8.     //#1 다운로드 반복 구문
  9.     dispatch_apply(addresses.count, queue) { (i: Int) -> Void in
  10.  
  11.       let address = addresses[i]
  12.       let url = NSURL(string: address)
  13.  
  14.       dispatch_group_enter(downloadGroup)
  15.  
  16.       let item = Download(url: url!) {
  17.         image, error in
  18.         if error != nil {
  19.           storedError = error
  20.         }
  21.         dispatch_group_leave(downloadGroup)
  22.       }
  23.       ItemManager.sharedManager.addItem(item)
  24.     }
  25.  
  26.     //#2 다운로드 완료시 실행되는 부분
  27.     dispatch_group_notify(downloadGroup, GlobalMainQueue){
  28.  
  29.       if let completion = completion {
  30.         completion(error: storedError)
  31.       }
  32.     }
  33.   }

Dispatch Block

iOS8에서 새로 추가된 Dispatch Block은 기존에 사용하던 클로져와 같은 개념이나 추가된 기능을 포함하고 있다. QoS와 같은 큐안에서 우선순위를 정할 수 있는 기능이 추가 되었으며, 무엇 보다도 취소(Cancelable) 가능해졌다. 하지만 기억해둬야 할 점은 취소라는 기능이 뜻하는 것은 대기중인 과업이 큐에서 빼주는 것을 의미한다. 그러므로 만약 과업이 자기 차례가 돌아와 벌써 실행이 시작되었으면 과업은 끝까지 실행을 하고 말것이다.

  • dispatch_block_create
  • dispatch_block_cancel

  1. //#1. dispatch_block을 사용해 dispatch_async 사용하기
  2. let queue = dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)
  3. let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS){
  4.     println("Hello")
  5. }
  6. dispatch_async(queue, block)
  7. .
  8. .
  9. .
  10. //#2. 취소하기
  11. dispatch_block_cancel(block)

GCD Testing

GCD는 멀티스레드 프로그래밍을 보다 쉽고 효율적으로 관리 실행해주게끔 도와주는 것 이상으로 단위 테스트를 할 때도 유용하게 사용할 수 있다. 예를 들어 비동기식 서버통신을 테스트를 할 땐 GCD에서 재공하는 Semaphore나 XCTest 프레임 워크에서 제공하는 XCTestExpectation을 통해서 테스트할 수있다. 필자는 해당 기능이 소개되기 전 비동기식 서버통신에 관련된 단위 테스트를 짜기위해 여러가지 고민했던 기억이 난다. 그때 당시 내린 결론은 비동기식 단위 테스트는 불가능하니 서버 통신이 가지고 올 수있는 모든 단위 케이스들을 테스트했던 기억이 난다. 아무튼 비동기식 단위 테스트를 어떻게 할 수 있는지 살펴보자.

  • Semaphores: 프로세스간 메세지 전송을 하거나, 혹은 공유 자원을 통해서 특정 data를 공유하게 될 경우 발생하는 문제는, 공유된 자원에 여러개의 프로세스가 동시에 접근을 하면서 발생한다. 단지 한번에 하나의 프로세스만 접근 가능하도록 만들어 줘야 하고, 이때 세마포어를 쓴다. 출처 보통 스레드 차원에서는 뮤텍스, 프로세스 차원에서는 세마포어를 통해 멀티 스레드 환경에서 개발하게 된다.
  • expectation: XCTest 프래임워크에서 비동기식 코드를 테스트할 수 있겠금 제공해주는 방법이다.

Semaphores

세마포어는 한 공유자원에 접근할 수 있는 Thread의 수를 정의 해놓고, 접근을 제어하는 도구이다. 보통 ‘뮤텍스랑 세마포어’를 비교할 때는 화장실 예를 사용한다. 화장실를 사용하기 위해서는 화장실 열쇠가 있어야 한다. 뮤텍스는 하나의 열쇠를 가지고 화장실 전체를 사용할 수있는데, 만약 앞사람이 사용중이라면, 다른 사용자의 접근을 차단하고 사용이 끝나면 다른 대기자가 사용을 할수 있도록 허용하는 방식이라고 할 수있다. 반면 세마포어는 뮤텍스와 맞찬가지로 화장실을 사용하기 위해서는 열쇠가 필요한데, 각 비어있는 칸마다 열쇠를 나누어주고 화장실 입구 지키미가 접근을 관리하는 방식이다. 만약 남은 열쇠가 없다면 대기해야 한다.

자 그럼, GCD를 사용하여 Semaphore를 사용하는 방법을 알아보자.

  1. func downloadImageURLWithString(urlString: String) {
  2.     let url = NSURL(string: urlString)
  3.     let semaphore = dispatch_semaphore_create(0) // 1
  4.     let photo = DownloadPhoto(url: url!) {
  5.       image, error in
  6.       if let error = error {
  7.         XCTFail("\(urlString) failed. \(error.localizedDescription)")
  8.       }
  9.       dispatch_semaphore_signal(semaphore) // 2
  10.     }
  11.  
  12.     let timeout = dispatch_time(DISPATCH_TIME_NOW, DefaultTimeoutLengthInNanoSeconds)
  13.     if dispatch_semaphore_wait(semaphore, timeout) != 0 { // 3
  14.       XCTFail("\(urlString) timed out")
  15.     }
  16. }

차단을 원하는 자원에 대해서 서메포어를 생성하면 해당자원을 가르키는 세마포어 값이 할당된다. 이 값이 0이면, 다른 프로세스는 해당 자원에 접근할 수 없고, 0 보다 크면 해당 자원에 접근할 수 있는 개념이다. dispatch_semaphore_create(0)을 통해서 세마포어 개체를 초기화하고, 과업이 다 끝나면 dispatch_semaphore_signal(semaphore)를 통해서 다른 프로세스의 접근을 허용할 수 있겠금 신호를 보내준다. dispatch_semaphore_wait(semaphore, timeout)를 통해서 코드 진행을 정해진 시간만큼 블럭해준다.

expectation

XCTest에서 제공해주는 메카니즘으로 expectation 개체를 생성하고 작업이 다 끝나 해당 개체의 fulfill()이 불려지기 전까지 코드 진행을 멈춰서 기다리게 하는 방법이다. 예제 코드는 다음과 같다.

  1. func downloadImageURLWithString(urlString: String) {
  2.   let url = NSURL(string: urlString)
  3.   let downloadExpectation = expectationWithDescription("Image downloaded from \(urlString)") // 1
  4.   let photo = DownloadPhoto(url: url!) {
  5.     image, error in
  6.     if let error = error {
  7.       XCTFail("\(urlString) failed. \(error.localizedDescription)")
  8.     }
  9.     downloadExpectation.fulfill() // 2
  10.   }
  11.  
  12.   waitForExpectationsWithTimeout(10) { // 3
  13.     error in
  14.     if let error = error {
  15.       XCTFail(error.localizedDescription)
  16.     }
  17.   }
  18. }

문서 History

Update: 2015-09-15

Dispatch Group에 대한 간략한 설명 추가.

[iOS] 로컬라이즈 텍스트 하기 (Localized String)

앱을 만들 때 이제는 국제화를 고려하지 않을 수 없는 시대가 되었다. 앱에서 사용되는 텍스트를 국제화하기 위해 애플에서는 Localization과 Internationalization을 제공하는데 이 블로그를 통해 Localization하는 방법을 간단히 알아본다.

 

A. 세팅하기.
1. 새 파일 추가한다. iOS->Resource->String File. 파일명은 ‘Localizable.strings’ 설정.
2. 추가하고 싶은 언어를 Project->Info->Localization에서 선택한다. (사진참조)
Screen Shot 2015-01-26 at 10.58.28 AM

3. Localizable.strings 파일을 선택하고, File Inspector 창을 열어서 Localize 버튼을 클릭한다. 그리고 나서 Drop down UI에서 ‘English’를 선택한다. (사진참조)

Screen Shot 2015-01-26 at 11.02.36 AM

4. 추가를 하면 오른쪽에 위치한 File Inspector의 Localization 항목에 English가 선택되어 있을 것이다. 새로 추가한 언어도 클릭하여 추가한다. (사진참조)

Screen Shot 2015-01-26 at 11.06.38 AM

 

5. 왼쪽 새로 추가된 Localizable.strings(Spanish)를 클릭하여 Key/Value형식으로 파일을 작성하면 세팅은 끝이다.

Screen Shot 2015-01-26 at 11.08.04 AM

 

B. 사용하기.
1. 파일 안에 형식은 “KEY” = “CONTENT”; 형식으로 한다.
2. 코드안에서 사용할 때는 ‘NSLocalizedString(@”"), nil)’ 메크로를 호출하여 사용한다.

[iOS] Keychain 기본 개념과 Wrapper Class 제공

Keychain은 무엇인가?

Keychain은 Mac OS X, iOS의 다양한 응용 프로그램에서 사용되는 비밀번호를 저장하는 암호화 되어 있는 저장소이다. 그런데 이것이 왜 필요한가 궁금해 할지 모른다. 예를 들어보자면, 맥북 사용자 혹은 iOS 기기 사용자는 여러가지 앱들을 사용한다. 메세지 앱, 소셜 앱, 유틸 앱, 금융 앱 등등. 각 앱마다 혹은 각 서비스를 사용하기 위해서는 ‘사용자 인증’ 과정이 필요하다. 본인임을 인증하는 과정은 현재 2015년 1월까지도 대부분이 ID & Password 방식으로 인증하는 방식을 사용하고 있다. 그렇기 때문에 각각의 비밀번호를 외우고 있어야하는 번거러움이 생긴다. 모든 서비스에 같은 비밀 번호를 설정하든지, 혹은 각 서비스 별로 사용하는 비밀번호를 안전한 곳에다가 적어두고 사용할 때마다 꺼내 보든지 해야하는데, 이러한 번거러움을 해결하고자 만들어진 것이 바로 Keychain이다. Macbook 사용자들은 Keychain에 저장되어 있는 비밀번호를 사용하기 위해서 컴퓨터 root 권한의 비밀번호를 치기만 하면 각각 앱, 혹은 서비스에서 Keychain에 저장한 오래되어 기억나지 않는 비밀번호를 사용할 수 있다. iOS 같은 경우에 Keychain은 각 앱에서 저장한 정보를  가지고 올 수 있는데, Provisioning profile 별로 사용경로가 구분이 된다. 쉽게 말하자면 같은 앱이라 할찌라도 만약 개발 단계에서 Profile이 바뀐다면 그전에 Keychain에 저장해 둔 정보를 빼올 수 없을 것이다.

 

언제 Keychain을 사용할 것인가?

앱 개발자 입장에서 생각해보자. 기기에 데이터를 저장을 할 수 있는 여러가지 방법이 존재한다. 각자의 경험에 따라 떠오르는 것이 다르겠지만,  기본적으로는 데이터베이스 혹은 Core Data, NSDefaultUser, Keychain, .plist 파일 등등. 저장할 방법도 다양하며 각각마다 만들어진 목적이 다르다. 그럼 Keychain은 언제 사용할 것인가? Keychain의 가장 큰 장점은 ‘보안’이다. 그렇기 때문에 사용자의 소중한 정보를 저장하고자 한다면 Keychain을 사용하는 것이 옳다. (분명 다른 매체를 통해서 데이터 저장이 가능하다. 분명히 해둘 것은 불가능한 것이 아니라 비밀번호, 혹은 각종 서비스의 API를 사용할 때 사용되는  secret Key, 혹은 Access Token 같은 보안이 중요한 데이터는 암호화를 하여 저장 관리하는 Keychain에 다가 저장하는 것이 옳은 방법이라는 것이다.)

 

어떻게 Keychain 서비스를 사용하는가?

애플에서는 Keychain 서비스 사용방법을 설명하기 위해 샘플소스를 공개하고 있다. Stack Overflow에 관련 질문에 대한 도 있으니 한번 살펴보기 바란다. 그리고 개인 Github에 Swift용과 Objective-C용으로 올려놓았으니 한번 사용해보기 바란다. 헤더 파일은 아래와 같이 간단하다. 저장하고자 하는 정보가 있다면 key와 함께 NSData 형식으로 넘기면 된다.

  1. @interface KeychainHelper : NSObject
  2.  
  3. + (BOOL)saveWithKey:(NSString *)key andData:(NSData *)data;
  4. + (NSData *)loadWithKey:(NSString *)key;
  5. + (BOOL)deleteWithKey:(NSString *)key;
  6. + (BOOL)clear;
  7.  
  8. @end

Dynamic type support

Screen Shot 2014-05-29 at 4.39.24 PM

 

iOS7에서 지원하는 기능 중에 글자 크기를 바꿀수 있는 기능이 있다. OS에서 지원하는 앱 뿐만이 아니라 만약 서드 파티에서도 해당 기능을 구현했다면 동일하게 기능을 사용할 수 있다. 간단하게 어떻게 적용할 수 있는지에 대해서 적어 보았다.

 

1. Register Notification

가장 먼저해야 하는 것은 시스템에서 dispatch하는 notification을 잡기 위해서 옵저버를 등록해야한다.

  1. [[NSNotificationCenter defaultCenter]
  2.     addObserver:self
  3.     selector:@selector(preferredContentSizeChanged:)
  4.     name:UIContentSizeCategoryDidChangeNotification
  5.     object:nil];

 

2. Make a handler method

핸들러 메서드 안에서 UIFont 클래스의 preferredFontForTextStyle: 메서드를 통해서 글꼴 스타일을 적용한다.

  1. - (void)preferredContentSizeChanged:(NSNotification *)notification
  2. {
  3.     [self.tableView reloadData];
  4.  
  5.     //OR
  6.  
  7.     self.mainTextView.font = [UIFont preferredFontForTextStyle:UIFontTextStyleBody];
  8. }

 

해외 튜터리얼 참조

[iOS7 UI Programming - part 1] Animation Transitioning

Animation Transitioning in iOS7

20130313_osxdock_minimize_genie

많은 개발자들은 UI 개발에 있어서 애니메이션이란 자신이 만든 프로덕에 있어서 선물 보장지와 같은 생각을 가지고 있는 것 같다. 중요하지 않지만 있으면 좋을 것 같고, 유행에 예민할 것 같은 주제라고나 할까? 뭐 사실 소프트웨어 개발에 있어서 트랜드에 예민한 디자인 요소라는 것을 부정하지는 않는다. 하지만 Mac OS X에서는 위 이미지와 같은 요술 램프 지니 애니메이션은 10년이 넘은 효과이기에 위 효과만 보더라도 아직까지 쓰이고 있는 것으로 보아 어찌보면 유행이라는 단어와 어울리지 않을지도 모른다는 생각을 해본다.

이번 주제는 iOS7의 Animation Transitioning에 대한 샘플 코드를 올려보고자 한다. iPhone에 있어서 UINavigationController 같은 앱 전체의 굵은 뼈대가 되는 컴포넌트에서 Push라든지 MadalView를 보여주는 기본 애니메이션이 이제 사용자들에게 슬슬 지루감을 주고 있기 때문일까? iOS7에서 해당 기술을 지원하는 코드가 잘 녹아들어있다. UINavigationController나 UIViewController 클래스만 보더라도 Delegate Protocol들을 통해서 이를 완벽하게 ‘레고 블럭’처럼 잘 설계를 해놓았다. UIViewControllerTransitioningDelegate이나 UINavigationControllerDelegate에 정의된 method에서 따로 구현된 클래스 객체만 잘 리턴해도 멋진 자신만의 애니메이션을 구현할 수 있다.

 

Output

 

Source Code

위 동영상의 커스텀 Animation Transitioning은 두가지이다. 첫 번째 ModalView가 나올 때 적용된 효과는 공이 튀기는 것과 같은 효과를 주는 바운드 효과이고 살아질 때 나오는 것은 줄어드는 효과인데, 임의대로 만든 효과이다. 먼저 살펴보고자 하는 클래스는 UITableViewController를 상속 받고있는 MasterViewController이다.

  1. #import "MasterViewController.h"
  2. #import "DetailViewController.h"
  3. #import "AppDelegate.h"
  4. #import "Cat.h"
  5. #import "BouncePresentAnimationController.h"
  6. #import "ShrinkDismissAnimationController.h"
  7.  
  8. #pragma mark – Interface of 'MasterViewController'
  9.  
  10. @interface MasterViewController ()<UIViewControllerTransitioningDelegate>
  11.  
  12. @end
  13.  
  14. #pragma mark – Implementation of 'MasterViewController'
  15.  
  16. @implementation MasterViewController
  17. {
  18.     BouncePresentAnimationController *_bounceAnimationController;
  19.     ShrinkDismissAnimationController *_shrinkDismissAnimationController;
  20. }
  21.  
  22. #pragma mark – Getters & Setters
  23.  
  24. - (NSArray *)cats
  25. {
  26.     return ((AppDelegate *)[[UIApplication sharedApplication] delegate]).cats;
  27. }
  28.  
  29. #pragma mark – Life cycle
  30.  
  31. - (instancetype)initWithCoder:(NSCoder *)aDecoder
  32. {
  33.     if (self = [super initWithCoder:aDecoder])
  34.     {
  35.         _bounceAnimationController = [BouncePresentAnimationController new];
  36.         _shrinkDismissAnimationController = [ShrinkDismissAnimationController new];
  37.     }
  38.  
  39.     return self;
  40. }
  41.  
  42. - (void)viewDidLoad
  43. {}
  44.  
  45. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  46. {}
  47.  
  48. #pragma mark – Table View
  49.  
  50. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  51. {}
  52.  
  53. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  54. {}
  55.  
  56. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  57. {}
  58.  
  59. #pragma mark – Protocol of 'UIViewControllerTransitioningDelegate'
  60.  
  61. //#1.
  62. - (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
  63. {
  64.     return _bounceAnimationController;
  65. }
  66.  
  67. //#2.
  68. - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
  69. {
  70.     return _shrinkDismissAnimationController;
  71. }

위 코드는 Class extension에 UIViewControllerTransitioningDelegate를 implement한다고 선언한 후, animationControllerForPresentedController:presentingController:sourceController: 메서드와 animationControllerForDismissedController:메서드를 구현하여, 애니메이션이 구현 되어 있는 클래스 객체를 반환만 해주면 된다.

다음은 _bounceAnimationController 객체의 클래스 소스코드이다.

  1. #pragma mark – Interface of 'BouncePresentAnimationController'
  2. @import Foundation;
  3. @interface BouncePresentAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
  4. @end
  5.  
  6. #pragma mark – Implementation of 'BouncePresentAnimationController'
  7. @implementation BouncePresentAnimationController
  8.  
  9. //0.
  10. - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
  11. {
  12.     return 0.5;
  13. }
  14.  
  15. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
  16. {
  17.     //1. obtain state from the context
  18.     UIViewController *toViewController = nil;
  19.     toViewController = [transitionContext
  20.                         viewControllerForKey:UITransitionContextToViewControllerKey];
  21.     CGRect finalFrame = [transitionContext finalFrameForViewController:toViewController];
  22.     UIViewController *fromViewController = nil;
  23.     fromViewController = [transitionContext
  24.                           viewControllerForKey:UITransitionContextFromViewControllerKey];
  25.  
  26.     //2. obtain the container view
  27.     UIView *containerView = [transitionContext containerView];
  28.  
  29.     //3. set initial state
  30.     CGRect screenBounds = [[UIScreen mainScreen] bounds];
  31.     toViewController.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
  32.  
  33.     //4. add the view
  34.     [containerView addSubview:toViewController.view];
  35.  
  36.     //5. animate
  37.     NSTimeInterval duration = [self transitionDuration:transitionContext];
  38.  
  39.     [UIView animateWithDuration:duration
  40.                           delay:0.0
  41.          usingSpringWithDamping:0.6
  42.           initialSpringVelocity:0.0
  43.                         options:UIViewAnimationOptionCurveLinear
  44.                      animations:^{
  45.                          fromViewController.view.alpha = 0.2;
  46.                          toViewController.view.frame = finalFrame;
  47.                      }
  48.                      completion:^(BOOL finished) {
  49.                          //6. Set completed! (Important!)
  50.                          [transitionContext completeTransition:YES];
  51.                          fromViewController.view.alpha = 1.0;
  52.                      }];
  53. }
  54.  
  55. @end

 

BouncePresentAnimationController 코드 분석

0. transitionDuration: 메서드는 이름을 보면 알 수 있듯이 transition 타임을 결정 해준다.
1. 새로들어 올 ViewController과 사라지게 될 ViewController를 assign 해준다.
2. transitionContext에는 containerView라는 view가 있다. Transitioning중에 보여질 View 객체들은 ContainerView 안에 있어야 한다.
3. toViewController의 Frame을 세팅한다.
4. ContainerView에 toViewController를 추가한다.
5. UIView의 animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:를 통해 바운스 애니메이션을 구현한다.

다음은 _shrinkDismissAnimationController의 클래스 코드를 살펴보자.

  1. #pragma mark – Interface of 'ShrinkDismissAnimationController'
  2.  
  3. @import Foundation;
  4. @interface ShrinkDismissAnimationController : NSObject<UIViewControllerAnimatedTransitioning>
  5. @end
  6.  
  7. #pragma mark – Implementation of 'ShrinkDismissAnimationController'
  8.  
  9. @implementation ShrinkDismissAnimationController
  10.  
  11. - (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
  12. {
  13.     return 0.5;
  14. }
  15.  
  16. - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
  17. {
  18.     //1.
  19.     UIViewController *toViewController = nil;
  20.     toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  21.     UIViewController *fromViewController = nil;
  22.     fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  23.     CGRect finalFrame = [transitionContext finalFrameForViewController:toViewController];
  24.     UIView *containerView = [transitionContext containerView];
  25.  
  26.     toViewController.view.frame = finalFrame;
  27.     toViewController.view.alpha = 0.5;
  28.  
  29.     //2
  30.     [containerView addSubview:toViewController.view];
  31.     [containerView sendSubviewToBack:toViewController.view];
  32.  
  33.     //3. Actual animation
  34.     CGRect fromViewFrame = fromViewController.view.frame;
  35.     CGRect screenBounds = [[UIScreen mainScreen] bounds];
  36.     CGRect shrunkenFrame = CGRectInset(fromViewFrame, fromViewFrame.size.width/4, fromViewFrame.size.height/4);
  37.     CGRect fromFinalFrame = CGRectOffset(shrunkenFrame, 0, screenBounds.size.height);
  38.     NSTimeInterval duration = [self transitionDuration:transitionContext];
  39.  
  40.     //4. animate
  41.     [UIView
  42.      animateKeyframesWithDuration:duration
  43.      delay:0.0
  44.      options:UIViewKeyframeAnimationOptionCalculationModeCubic
  45.      animations:^
  46.     {
  47.  
  48.         [UIView addKeyframeWithRelativeStartTime:0.0
  49.                                 relativeDuration:0.5
  50.                                       animations:^{
  51.                                           fromViewController.view.transform = CGAffineTransformMakeScale(0.5, 0.5);
  52.                                           toViewController.view.alpha = 0.5;
  53.                                       }];
  54.  
  55.         [UIView addKeyframeWithRelativeStartTime:0.5
  56.                                 relativeDuration:0.5
  57.                                       animations:^{
  58.                                           fromViewController.view.frame = fromFinalFrame;
  59.                                           toViewController.view.alpha = 1.0;
  60.                                       }];
  61.     }
  62.      completion:^(BOOL finished)
  63.     {
  64.         [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
  65.     }];
  66. }
  67.  
  68. @end

 

ShrinkDismissAnimationController 코드 분석

1. 새로들어 올 ViewController과 사라지게 될 ViewController를 assign 해준다.
2. transitionContext에는 containerView라는 view가 있다. Transitioning중에 보여질 View 객체들은 ContainerView 안에 있어야 한다.
3. 애니메이션에 필요한 값
4. 실제 애니메이션 구현은 UIView의 animateKeyframesWithDuration:delay:options:options:animations:completion: 안에 또 다른 애니메이션 메서드를 구현함을 통해 원하는 애니메이션을 구현한다.

 

마치며..

UI 프로그래밍이 시대의 유행이든 아니든 간에 SDK의 설계를 보며 느끼는 점이 많았던 리서치 같다. 더 공부하고 더 열심히 해야겠다는 생각을 해본다.

[Facebook-Tweaks] 간단하게 살펴보기.

Tweaks Framework by FB

이틀 전 (May.24.2014) 페이스북에서 iOS 프래임워크 하나를 Github에 올리면서 공개를 했다. 해당 프래임워크의 주 기능은 간단하다. 완성된 output의 사소한 속성들을 변경하고 싶을 때 개발자의 손에 걸치지 않고 Runtime으로 수정 가능한 기능을 제공해 준다는 것이다. (아마 무슨 뜻인지 이해가 가질 않을 것으로 생각된다.)

 

“Why do I need it?”

예를 들어 개발자가 애니메이션의 Duration을 정해서 컴파일을 할 것이다. 그런데 모션 그래픽 디자이너라든지 기획자가 보기에 너무 길다던가 맘에 안들 수 있다. 그런데 일일이 개발자 옆에 앉아서 테스트 해보거나 혹은 Bug Tracking System 같은 경로를 통해서 이와 같은 다양한 주문을 시도할 것이다. 하지만 이런 과정은 프로젝트 전체를 보았을 때 상당히 값이 비싼 비용이다. 개발자와 디자이너와 신경전을 한 두번 해본 개발자라면 이 프래임워크의 큰 가치를 깨달을 것이다.

 

“What is ‘Tweaks’?”

Tweaks는 이런 문제점을 해결해주는 좋은 대안이 될 수 있다. 프래임워크를 사용하여 디자이너 혹은 기획자가 직접 UI의 속성을 바꿔가면서 테스트해볼 수 있는 대안이 생겼기 때문이다.

from github

위 gif 이미지를 잠시 보자. ‘Tweaks’라는 UILabel 객체가 화면에 있다. 그런데 어떤 이유에서든지 디자이너가 색상이 맘에 들지 않아서 이를 변경하고자 한다. 일반적으로 디자이너는 개발자에게 이메일을 보내거나 가끔은 자리에 찾아와 색상 변경을 요구하며 이것 저것을 테스트 해보길 요구할 것이다. 이럴 때에 개발자에게 있어서는 얼마나 방해가 되는 일인지 모르며 이런 작은 방해들이 점점 모여서 프로젝트의 비용을 점점 비싸게 만드는 것이다. 그런데 (개발하기 나름이겠지만… ) 본 프래임워크를 적용해서 폰을 흔들면 UI 속성을 바꿀수 있는 TableView가 나온다. 그럼 해당 UI의 속성을 바꿈으로 개발자의 손을 빌리지 않고 이것 저것 tweaking을 할 수 있는 길이 생긴 것이다.

 

“How to use?”

github 페이지에 설치 방법 부터 사용 방법이 잘 나와 있다. 가장 간단히 설치할 수 있는 방법은 CocoaPod를 사용하는 것이다. 소스코드의 순서를  간단하게 보면 다음과 같을 것이다.

  1. 프래임워크 Import 함.
  2. FBTweakViewController를 화면에 뿌릴수 있는 장치를 만듬.
  3. UI 객체 인스턴스의 속성에 바인딩 혹은, ‘FBTweaksValue’를 통해  설정 값 할당 함.

 

프래임워크 Import 하기

  1. #import <FBTweak/FBTweak.h> //FBTweak 기본 헤더파일
  2. #import <FBTweak/FBTweakShakeWindow.h> //폰을 흔들어 FBTweaksViewController를 열고 싶을 때 사용하기
  3. #import <FBTweak/FBTweakInline.h> //FBTweakInline 객체 사용할 때 필요
  4. #import <FBTweak/FBTweakViewController.h> //FBTweakViewController를 presentViewController:animated:completion:을 통해 열때 필요

 

FBTweakViewController를 화면에 뿌릴 장치

변경 가능한 UI 속성을 제어할 UITableView를 화면에 나타내주기 위해서 크게 두 가지 방법이 있을 것이다. 첫 번째는 폰을 흔들어서 나타나게 하는 방법과 임시의 버튼을 만들어서 presentViewController:animated:completion: 메서드를 사용하여 FBTweakViewController 객체를 화면서 나타나게 하는 방법이다. 둘 중에 편한 방법으로 사용하면 될 것이라고 생각 되어진다.

 

UI 속성 바인딩 혹은 설정 값을 Tweaks 함수를 통해 할당

이제 자신이 디자이너들이나 타인들에게 Runtime 때에 변경 가능한 UI 속성을 주고자 하는 코드에 FBTweakValue 함수를 통해서 값을 할당 하든지, FBTweakBind 함수를 통해서 값을 바인딩하면 된다.

  1. //#. Binding 예
  2. UILabel *_label = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, _window.bounds.size.width, _window.bounds.size.height * 0.75)];
  3. _label.textAlignment = NSTextAlignmentCenter;
  4. _label.numberOfLines = 0;
  5. _label.userInteractionEnabled = YES;
  6. _label.backgroundColor = [UIColor clearColor];
  7. _label.textColor = [UIColor blackColor];
  8.  
  9. //1-a
  10. _label.font = [UIFont systemFontOfSize:FBTweakValue(@"Content", @"Text", @"Size", 60.0)];
  11.  
  12. //1-b. UILabel 객체의 텍스트 속성을 동적인 값으로 바인딩
  13. FBTweakBind(_label, text, @"Content", @"Text", @"String", @"Tweaks");
  14.  
  15. //1-c. UILabel 객체의 알파 속성을 동적인 값으로 바인딩
  16. FBTweakBind(_label, alpha, @"Content", @"Text", @"Alpha", 0.5, 0.0, 1.0);

‘FBTweakBind’ 함수 사용법은 다음과 같다.

  1. FBTweakBind(object_, property_, category_, collection_, name_, …)

  • object: UI 오브젝트 인스턴스
  • property: 객체 속성명
  • category: TableView에 나올 TableViewCell 명
  • collection: grouped tableView에 표시 될 그룹 명
  • name: 해상 TableViewCell에 표시 될 title 명
  • …: 기본 설정 값

‘FBTweakValue’ 함수 사용법은 다음과 같다

  1. FBTweakValue(category_, collection_, name_, …)

  • category: TableView에 나올 TableViewCell 명
  • collection: grouped tableView에 표시 될 그룹 명
  • name: 해상 TableViewCell에 표시 될 title 명
  • …: 기본 설정 값

그리고 나서 뒤에 오는 값들은 기본 설정 값이다. 이 기본 설정에 따라 TableViewCell에 나타날 설정 가능하도록 해주는 UI가 결정이 된다. 1-b에는 NSString인 ‘Tweaks’로 설정되어 있고, 1-c에서는 세개의 값이 있다. 첫 번째 값인 0.5는 디폴트 값이며, 두번째는 설정 가능한 가장 낮은 값, 세번째 값은 설정 가능한 가장 큰 값이다. 이렇게 설정을 하면 UIStepper를 통해 설정이 가능하게 해준다. 만약에 이 값에 YES와 같은 Bool 값이 들어오면 Toggle 가능한 UISwitch가 들어 올 것이다.

Where to go next?

github에 올라온 소스를 자신의 컴퓨터에 클론하여 예제 앱을 사용해 보면 자신의 프로젝트에 어떻게 사용할지 쉽게 알게 될 것이다. 위에 설명한 것들은 정말 기본적이고 쉬운 것들이고, 더 advance한 사용은 github에 나와있는 설명과 stackoverflow를 통해 알아보기 바란다. 혹자도 기존 프로젝트에 적용해보고 추가할 내용이 생기면 추후에 본 블로그를 더 업데이트 하겠다.

[iOS7 DynamicAnimator] 샘플 코드

Screen Shot 2014-03-21 at 11.20.01 AM

iOS7에는 비-게임을 위한 물리엔진이 장착되어 있다. UIDynamicAnimator를 사용하여 간단한 실험을 해볼 수 있다. 코드에 대한 설명은 코드 아래 설명을 참조하라.

  1. #pragma mark – Interface of 'ViewController'
  2.  
  3. @interface ViewController ()<UICollisionBehaviorDelegate>
  4. @end
  5.  
  6. #pragma mark – Implementation of 'ViewController'
  7.  
  8. @implementation ViewController
  9. {
  10.     //#0
  11.     UIDynamicAnimator *_animator;
  12.     UIGravityBehavior *_gravity;
  13. }
  14.  
  15. #pragma mark – Life cycle
  16.  
  17. - (void)viewDidLoad
  18. {
  19.     [super viewDidLoad];
  20.  
  21.     //#1
  22.     UIView *red = [self boostupRedRectangle];
  23.     UICollisionBehavior * collision = [self boostupAnimationAndBehaviorApplyToView:red];
  24.  
  25.     //#2
  26.     UIView *yellow = [self boostupYellowRectangle];
  27.     [self addBarrierOnCollision:collision
  28.                 withBarrierView:yellow
  29.           andBoundaryIdentifier:@"yellow"];
  30.  
  31.     //#3
  32.     UIView *blue = [self boostupBlueRectangle];
  33.     [self addBarrierOnCollision:collision
  34.                 withBarrierView:blue
  35.           andBoundaryIdentifier:@"blue"];
  36. }
  37.  
  38. - (void)didReceiveMemoryWarning
  39. {
  40.     [super didReceiveMemoryWarning];
  41.     // Dispose of any resources that can be recreated.
  42. }
  43.  
  44. #pragma mark – Protocol of 'UICollisionBehaviorDelegate'
  45.  
  46. - (void)collisionBehavior:(UICollisionBehavior*)behavior
  47.       beganContactForItem:(id <UIDynamicItem>)item
  48.    withBoundaryIdentifier:(id <NSCopying>)identifier
  49.                   atPoint:(CGPoint)p
  50. {
  51.  
  52. }
  53.  
  54. #pragma mark – Boostup UIs
  55.  
  56. - (UIView *)boostupRedRectangle
  57. {
  58.     CGRect redFrame = CGRectMake(50, 50, 100, 100);
  59.     UIView *redRectangle = [[UIView alloc] initWithFrame:redFrame];
  60.     [redRectangle setBackgroundColor:[UIColor redColor]];
  61.     [self.view addSubview:redRectangle];
  62.  
  63.     return redRectangle;
  64. }
  65.  
  66. - (UIView *)boostupYellowRectangle
  67. {
  68.     CGRect yellowFrame = CGRectMake(0, 300, 90, 20);
  69.     UIView *yelloRectangle = [[UIView alloc] initWithFrame:yellowFrame];
  70.     [yelloRectangle setBackgroundColor:[UIColor yellowColor]];
  71.     [self.view addSubview:yelloRectangle];
  72.  
  73.     return yelloRectangle;
  74. }
  75.  
  76. - (UIView *)boostupBlueRectangle
  77. {
  78.     CGRect blueFrame = CGRectMake(180, 380, 140, 20);
  79.     UIView *blueRectangle = [[UIView alloc] initWithFrame:blueFrame];
  80.     [blueRectangle setBackgroundColor:[UIColor blueColor]];
  81.     [self.view addSubview:blueRectangle];
  82.  
  83.     return blueRectangle;
  84. }
  85.  
  86. - (UICollisionBehavior *)boostupAnimationAndBehaviorApplyToView:(UIView *)view
  87. {
  88.     //#2-1
  89.     _animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
  90.     _gravity = [[UIGravityBehavior alloc] init];
  91.     [_gravity addItem:view];
  92.     [_animator addBehavior:_gravity];
  93.     _gravity.magnitude = 4.0f;
  94.  
  95.     //#2-2
  96.     UICollisionBehavior *collision = [[UICollisionBehavior alloc]
  97.                                       initWithItems:@[view]];
  98.     collision.collisionDelegate = self;
  99.     collision.translatesReferenceBoundsIntoBoundary = YES;
  100.     [_animator addBehavior:collision];
  101.  
  102.     return collision;
  103. }
  104.  
  105. - (void)addBarrierOnCollision:(UICollisionBehavior *)collision
  106.               withBarrierView:(UIView *)barrier
  107.         andBoundaryIdentifier:(NSString *)identifier
  108. {
  109.     CGPoint startPoint = barrier.frame.origin;
  110.     CGPoint endPoint = CGPointMake(barrier.frame.origin.x + barrier.frame.size.width, barrier.frame.origin.y);
  111.     [collision addBoundaryWithIdentifier:identifier
  112.                                fromPoint:startPoint
  113.                                  toPoint:endPoint];
  114. }
  115.  
  116. @end

코드 설명

  1. ‘boostupAnimationAndBehaviorApplyToView:’ 메서드에서 보면 _animator가 애니메이션을 적용할 뷰를 넘기며 생성하는 것을 볼 수있다. 그리고 난 다음 UIGravityBehavior 클래스의 인스턴스를 애니메이터에 추가하는 것으로 해당 뷰에 중력 효과를 적용할 수 있다. 그리고 UICollisionBehavior 객체를 통하여서 빨강 네모를 화면안에 가둬넣을 수 있다.
  2. addBarrierOnCollision:WithBarrierView:AndBoundaryIdentifer: 메서드를 통하여서 UICollisionBehavior에 보이지 않는 장벽을 만들 수 있다. 실제로 화면에서 보이는 장애물은 그저 자리만 지키고 있을 뿐이며 UICollisionBehavior의 객체에 보이지 않는 장해물을 세팅하는 메서드이다.

[iOS7 Motion-Effect] UIInterpolatingMotionEffect 샘플 코드

UIInterpolatingMotionEffect

출처: google 이미지 검색 결과

iOS7을 보면 잠금 화면이라든지 바탕화면이 시각에 따라 움직이는 효과를 볼 수 있을 것이다. 애플에서 쉽게 라이브러리로 제공을 하고 있다.

  1. #import "ViewController.h"
  2.  
  3. @interface ViewController ()
  4.  
  5. @end
  6.  
  7. @implementation ViewController
  8.  
  9. - (void)viewDidLoad
  10. {
  11.     [super viewDidLoad];
  12.  
  13.     //1. Lower bg
  14.     UIImage *lowerImg = [UIImage imageNamed:@"bethel_in_carseat.jpg"];
  15.     UIImageView *lowerbg = [[UIImageView alloc] initWithImage:lowerImg];
  16.     lowerbg.frame = CGRectInset(self.view.frame, -50.0f, -50.0f);
  17.  
  18.     [self.view addSubview:lowerbg];
  19.     [self addMotionEffectToView:lowerbg magnitude:50.0f];
  20.  
  21.     //2. mid bg
  22.     UIImage *midImg = [UIImage imageNamed:@"Background-MidLayer.png"];
  23.     UIImageView *midbg = [[UIImageView alloc] initWithImage:midImg];
  24.     [self.view addSubview:midbg];
  25.  
  26.     //3. header bg
  27.     UIImage *headerImg = [UIImage imageNamed:@"Sarnie.png"];
  28.     UIImageView *headerBG = [[UIImageView alloc] initWithImage:headerImg];
  29.     headerBG.center = CGPointMake(220, 190);
  30.     [self.view addSubview:headerBG];
  31.     [self addMotionEffectToView:headerBG magnitude:-20.0f];
  32. }
  33.  
  34. - (void)didReceiveMemoryWarning
  35. {
  36.     [super didReceiveMemoryWarning];
  37. }
  38.  
  39. #pragma mark – Methods
  40.  
  41. - (void)addMotionEffectToView:(UIView *)view magnitude:(CGFloat)magnitude
  42. {
  43.     UIInterpolatingMotionEffect *horMoffec = nil;
  44.     horMoffec = [[UIInterpolatingMotionEffect alloc]
  45.                  initWithKeyPath:@"center.x"
  46.                  type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
  47.     horMoffec.maximumRelativeValue = @(magnitude);
  48.     horMoffec.minimumRelativeValue = @(-magnitude);
  49.  
  50.     UIInterpolatingMotionEffect *verMoffec = nil;
  51.     verMoffec = [[UIInterpolatingMotionEffect alloc]
  52.                  initWithKeyPath:@"center.y"
  53.                  type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
  54.     verMoffec.maximumRelativeValue = @(magnitude);
  55.     verMoffec.minimumRelativeValue = @(-magnitude);
  56.  
  57.     UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
  58.     group.motionEffects = @[verMoffec, horMoffec];
  59.  
  60.     [view addMotionEffect:group];
  61. }

[Build Setting::Other Linker Flags] -ObjC와 -all_load의 차이점

Other Linker Flag‘는 XCode 프로젝트에서 서드 파티 라이브러리를 사용할 때 흔히 세팅하는 build setting이다. 해당 플래그는 컴파일 타임 때에 서드 파티의 링크하는 과정에 대한 설정인데, 일반적으로 가장 많이 사용되는 Flag는 ‘-ObjC’와 ‘-all_load’ 이다. 언듯 보면 똑같은 역할을 하는 플래그 처럼 보이지만 다음과 같은 차이가 있다고 한다.

-Objc

This flag causes the linker to load every object file in the library that defines an Objective-C class or category.

해당 값은 링커가 Objective-C 클래스나 카테고리로 정의된 라이브러리에 속한 객체 파일들을 모두 적재하게 해주는 플래그이다.

-all_load

For 64-bit and iPhone OS applications, there is a linker bug that prevents -ObjC from loading objects files from static libraries that contain only categories and no classes. The workaround is to use the -all_load or -force_load flags. -all_load forces the linker to load all object files from every archive it sees, even those without Objective-C code

64 비트 프로젝트나 아이폰 OS 응용 프로그램에서 클래스가 없고 카테고리만 있는 Static 라이브러리의 객체를 -ObjC만 사용하여 적재하려고 할 때 정상 작동을 하지 않는 버그가 있다. 그 버그를 위해 대처 안으로 사용할 수 있는 것이 ‘-all_load‘나 ‘-force_load‘  플래그이다. ‘-all_load‘ 플래그는 모든 객체 파일을 링커로 부터 적재할 수 있도록 하는 플래그이다.

Reference Building Objective-C static libraries with categories

from: http://stackoverflow.com/questions/21844824/difference-between-all-load-and-objc