https://tapito.tistory.com/450


http://www.glimsoft.com/06/28/ios-8-today-extension-tutorial/



iOS 8부터 새로 도입된 Today Extension을 구현하는 예제입니다.

 

1. 프로젝트 및 어플리케이션 확장 생성

 Xcode를 열고 새 프로젝트를 생성합니다. 여기에서는 [iOS] - [Application] - [Single View Application]을 선택하고 Next를 누르겠습니다.

 

 앱의 이름을 지정하고 Next를 누르면 빈 화면의 iOS 어플리케이션 프로젝트가 생성됩니다.

 

 Today Extension은 본래의 앱과 따로 작동되는 또 하나의 작은 앱입니다. 이 프로젝트에서 Today Extension을 추가하기 위해 [File] - [New] - [Target...]을 클릭합니다.

 

 [iOS] - [Application Extension] - [Today Extension]을 선택 후 Next를 클릭합니다.

 

 적절한 이름을 지정한 후 Finnish를 클릭합니다. Bundle Identifier가 서로 다름을 통해 iOS 어플과 Today Extension이 따로 작동됨을 알 수 있습니다.

 

2. App Group 설정하기

 사실상 두 앱이 작동되는 것이므로 둘 사이에 데이터를 교환할 필요가 있을 경우에는 App Groups를 사용합니다. 위 화면에서 먼저 주 어플리케이션인 ExtensionApp을 클릭하고 [Capablities]를 클릭한 후 [App Groups] 옆의 스위치를 클릭하여 [On] 상태로 만듭니다.

** 주의사항 1: App Groups를 사용하기 위해서는 1년에 $99를 지불하고 유효기간이 남아있는 애플 개발자 ID가 필요합니다. 애플 서버를 통하는 인증이므로 기기 탈옥으로 이 제약을 우회할 수 없습니다.

 주 어플리케이션과 Today Extension 사이에 데이터를 교환할 필요가 없다면 이 과정이 필요하지 않습니다. $99를 지불하기에는 아까운데 꼭 데이터를 교환할 필요가 있다면 공인 IP로 접근 가능한 외부 서버를 하나 마련하여 데이터를 전송하고 대상 앱 또는 Today Extension이 그 데이터를 다시 전송받는 형식으로 약간의 꼼수를 사용할 수는 있습니다.

 

 

 개발자 ID로 로그인한 후 provision을 선택하고 [Choose] 버튼을 클릭합니다.

 

 이미 만들어 놓은 App Groups에서 하나를 선택할 수도 있고 하단의 [+] 버튼을 클릭하여 새 App Groups를 만들수도 있습니다.

** 주의사항 1: App Groups는 전 세계에서 유일한 이름이어야 합니다. group.compant.Test 류의 흔한 이름은 이미 누군가가 애플 개발자 서버에 등록했을 가능성이 높고 이 경우 오류가 뜨며 App Group 생성이 되지 않습니다. 본인이나 회사의 이름을 포함하여 최대한 중복을 피해야 합니다.

** 주의사항 2: 한번 등록한 App Groups는 지우기가 불가능합니다. 이름 지정에 신중해야 합니다.

 

 주 어플리케이션의 확장인 TodayExtensionApp도 마찬가지로 App Groups를 활성화합니다. 주 어플리케이션과의 데이터 공유를 위해 이전 단계에서 선택한 App Group과 같은 App Group를 선택해야 합니다.

 

3. 어플리케이션과 Today Extension에 대한 UI 작성 및 Outlet 연결

 문자열 교환을 확인하기 위해 UI를 대충 작성합니다. 이와 관련한 자세한 과정은 생략합니다. Today Extension과 관련된 UI는 Xcode 좌측 탐색 메뉴에서 MainInterface.storyboard에 있습니다.


 

 

4. 데이터 교환을 위한 코드 작성

 주 어플리케이션과 Today Extension 사이에 데이터를 교환하기 위해서는 NSUserDefaults 클래스를 사용합니다. 이 때 식별자는 App Groups에서 선택하거나 새로 만든 ID로서 주 어플리케이션과 Today Extension이 함께 공유하며 모두 접근 가능합니다.

4.1 주 어플리케이션 측 코드 ([확인] 버튼을 터치했을 때의 이벤트 처리)

/* ViewController.m */
- (IBAction) buttonOK_TouchDown:(id)sender
{
	/* suite name은 AppGroup에서 선택했던 식별자 중 하나를 지정합니다. */
	NSUserDefaults * sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.company.Test"];

	/* 공유되는 UserDefaults에 문자열 값을 저장합니다. forKey는 변수 이름으로, Today Extension도 이 이름이 붙은 변수를 불러올 것입니다. */
	[sharedUserDefaults setObject:self.textField.text forKey:@"MyString"];

	/* 변경 사항을 저장하고 이 UserDefaults를 공유하는 앱들에게 데이터가 변경되었음을 통지합니다. */
	[sharedUserDefaults synchronize];
}

 

4.2 Today Extension 측 코드 (같은 UserDefaults를 공유하는 다른 앱에서 Synchronize 메서드가 실행될때마다 자신의 값을 업데이트)

/* TodayViewController.m */
- (id) initWithCoder:(NSCoder *)aDecoder
{
	self = [super initWithCoder:aDecoder];

	if (self)
	{
		/* 시스템 수준에서 어떤 NSUserDefaults가 synchronize 되었음을 감지하면 이를 자신에게도 알려달라고 자신을 등록합니다. */
		/* 동일한 App Groups에 속한 다른 앱에서 NSUserDefaults가 변경될 때마다 userDefaultsDidChange가 실행될 것입니다. */
		[[NSNotificationCenter notificationCenter] addObserver:self selector:@selector(userDefaultsDidChange:) name:NSUserDefaultsDidChangeNotification object:nil];
	}

	return self;
}

- (void) viewDidLoad
{
	[super viewDidLoad];
	// Do any additional setup after loading the view form its nib.

	[self refresh];
}

- (void) userDefaultsDidChange:(NSNotification *)notification
{
	[self refresh];
}

- (void) refresh
{
	NSUserDefaults * sharedUserDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.company.Test"];

	/* UserDefaults에 변동 사항 발생 시 또는 사용자가 상태표시줄을 끌어 내려 이 UI가 보여질때마다 MyString 변수의 값을 다시 읽어옵니다. */
	self.labelContent.text = [sharedUserDefaults objectForKey:@"MyString"];
}

 

5. 컴파일 및 실행하기

 Today Extension을 생성하고 주 어플리케이션과의 데이터 교환을 위한 기본적인 작업은 끝났습니다. 컴파일 후 실행해보겠습니다.

 주 어플리케이션이 뜨는데, 상태 표시줄을 아래로 끌어 내리면 아래와 같이 Today Extension을 볼 수 있습니다.

 

 New Widget Available이라 알림이 뜨는데 Edit를 터치하여 현재 만든 Today Extension을 화면에 보이도록 추가합니다.

 

5.1 레이아웃 관련 오류 해결

 여기서 만든 Today Extension의 제목은 뜨는데 레이아웃에서 넣은 각종 Label 은 보이지 않습니다. 혹은 아래가 잘려서 보일 수도 있는데요, Interface Builder에서 작성한 Today Extension의 높이를 코드로 정확히 명시하면 이 문제가 해결됩니다. iOS Simulator로 테스트 하는 경우 화면 상단의 [iOS Simulater] - [Reset Content and Settings...]를 클릭하여 시뮬레이터를 초기화하고 실행하는 것을 권장합니다.

/* TodayViewController.m */
- (void) viewDidLoad
{
	[super viewDidLoad];
	// Do any additional setup after loading the view form its nib.

	[self setPreferredContentSize:CGSizeMake(320.0, 200.0)];
	[self refresh];
}

 

 작성한 UI가 그대로 Today Extension에 나타남을 확인할 수 있습니다. 그런데 iOS의 Today Extension에서 각 뷰들을 일종의 들여쓰기처럼 보여주고 있어 본래 작성한 UI보다 한 쪽으로 치우쳐져 있음을 확인할 수 있습니다. 이 현상을 수정하기 위해 Today Extension에 대한 Margin을 없애도록 아래와 같이 메서드를 하나 override합니다.

/* TodayViewController.m */
- (UIEdgeInsets) widgetMarginInsetsForProposedMarginInsets:(UIEdgeInsets)defaultMarginInsets
{
	/* 이 레이아웃에 대한 Margin을 0으로 합니다. */
	return UIEdgeInsetsZero;
}

 

 다시 실행하면 레이아웃이 제대로 보여짐을 확인할 수 있습니다.

 

 Today Extension을 닫고 주 어플리케이션으로 돌아가 label에 들어갈 텍스트를 수정해보겠습니다. [확인] 버튼을 눌러 Today Extension에게 UserDefaults가 수정되었음을 통지하면 아래와 같이 Today Extension이 UserDefault 내용을 다시 읽어 변경된 문자열을 출력해 줌을 확인할 수 있습니다.