2016년 9월 27일 화요일

iOS Multitasking, Background fetch 개념


[애플리케이션의 상태]


not running
사용자에 의해 아직 애플리케이션이 실행되고 있지 않거나 이미 실행된 후 사용자 혹은 운영체제에 의해 종료된 상태


foreground
애플리케이션이 현재 사용자에게 보여지고 있는 상태,오직 하나의 애플리케이션만이 포그라운드 상태가 될수 있다. 포그라운드 상태의 애플리케이션은 활성(active) 혹은 비활성(not active) 상태를 가진다

비활성 상태에서는 애플리케이션이 포그라운드 상태로 실행중이긴 하지만, 이벤트를 수신하거나 처리하고 있지 않는 상태다.(전화가 걸려오거나 화면 잠금 상태 등 시스템이 사용자의 응답을 기다리고 있는중)
반면, 활성 애플리케이션은 포그라운드에서 실행되고 있으며, 현재 이벤트를 수신하고 있는 상태이다.(사용자와의 상호작용이 있거나 네트워크를 통한 데이터 송수신)

background
백그라운드 상태는 애플리케이션이 더이상 포그라운드 상태가 아님을 의미
이 상태의 애플리케이션은 여전히 코드를 실행하고 있는데 작업을 끝마치기 위해 추가적인 수행을 요청하였거나 특별한 범주의 애플리케이션인 경우는 백그라운드에서의 코드 실행을 요청
사용자가 다른 애플리케이션을 실행시키거나 다른 백그라운드 애플리케이션을 포그라운드로 바꿀때가 있다. 이때 현재의 애플리케이션은 백그라운드 상태로 바뀐다.


Suspended
애플리케이션이 백그라운드 상태로 된후 더이상 코드를 실행하고 있지 않으면 Suspended 상태로 변경
이 상태의 애플리케이션은 여전히 메모리에 존재하며, 일시 중지 상태가 될 당시의 상태를 저장하고 있지만 CPU나 배터리를 소모하지 않는다
이 상태의 애플리케이션은 빠르게 포그라운드 상태로 전환하고 실행을 재시작할수 있기 때문에 즉각적인 애플리케이션의 전환이 가능하다.
Suspended 상태의 애플리케이션은 메모리 부족 등의 이유로 운영체제에 의해 언제든지 종료될수 있다. 따라서 애플리케이션은 기본적으로 Suspended 상태가 되기 전에 비휘발성 저장소에 데이터를 저장해야 한다.


[멀티태스킹 애플리케이션의 생명주기에 대한 개념]


didFinishLaunchingWithOptions
애플리케이션의 로드가 끝나면 didFinishLaunchingWithOptions 델리게이트 메서드가 호출

applicationDidBecomeActive
애플리케이션이 foreground 상태
애플리케이션이 백그라운드 -> 포그라운드로 이동하면 applicationDidBecomeActive 호출
다음에 applicationWillEnterForeground 메서드 호출

applicationDidEnterBackground
background 상태

applicationWillResignActive
foreground 애플리케이션이 background가 되면 비활성(inactive) 상태가 되며, 애플리케이션 델리게이트의 applicationWillResignActive 메서드가 호출된다.

applicationWillTerminate
애플리케이션 (사용자나 시스템의 요청으로) 종료되려 할때 애플리케이션 델리게이트의  메서드가 호출된다.

applicationDidEnterBackground 메서드의 반환이 발생하기 전에 수행하던 작업을 마무리할 5초간의 시간이 주어진다. 이 시간동안 작업을 처리하기 힘들면 beginBackgroundTaskWithExpirationHandler 메서드를 호출하고 작업이 완료되면 endBackgroundTask 메서드를 호출한다. 주어진 시간안에 applicationDidEnterBackground 메서드의 반환이 실패한다면 애플리케이션이 강제 종료 될것이다


[ 멀티태스킹 지원 체크]

   //
    func multitaskingSupported() -> Bool {
        let device = UIDevice.current
        var backgroundIsSupported = false
        
        if device.responds(to: #selector(getter: UIDevice.isMultitaskingSupported)){
            backgroundIsSupported = device.isMultitaskingSupported
        }
        return backgroundIsSupported
        
        
    }


[Xcode내 멀티태스킹 활성화하기]

Xcode로 개발되는 모든 애플리케이션은 디폴트로 멀티태스킹 지원이 비활성화되어 있다. 애플리케이션이 백그라운드 모드로 들어가도록 해야 하는 상황이라면, 그에 대한 설정은 애플리케이션의 info.plist 파일에서 해야 한다. 설정하는 방법은 프로젝트 내비게이터 패널의 상단에 있는 애플리케이션 타깃을 선택하고, Capabilities 탭을 선택하고 Background Modes 옵션을 Off에서 On으로 바꾸는 것이다. 아래와 같이 선택하면 멀티태스킹 지원을 활성화하기 위해 Info.plist파일에 적절한 설정이 추가되어야 한다.





[지원되는 백그라운드 실행 형태]
애플은 오디오, 위치 업데이트, VOIP, 뉴스스탠드 업데이트, 외부 액세서리와 블루투스 액세서리 통신, 백그라운드 fetch, 원격 알림(Remote notification)등 아홉가지의 애플리케이션들은 사용 경험상 백그라운드에서 실행이 중단되어서는 안된다고 생각하게 되었다. 애플리케이션이 백그라운드에서 실행되는 것은 UIBackgroundModes 키를 이용하여 애플리케이션의 Info.plist파일의 설정을 통해 지원한다. UIBackgroundModes 키의 값은 애플리케이션에 하나 이상의 백그라운드 실행 모드를 등록할수 있는 배열이다. 백그라운드 모드를 선택하기 위해서는 바로 위에서처럼 Capabilities 패널의 Background Modes에서 필요한 모드의 체크박스를 체크하면 된다.


[백그라운드 페치(Background fetch) 개요]

백그라운드 fetch는 백그라운드 상태에 있는 애플리케이션이 콘텐츠를 업데이트할수 있도록 백그라운드에서 잠자고 있는 애플리케이션을 깨우는 방법을 제공하는 것이다. 예를 들어 뉴스 애플리케이션은 백그라운드 상태에서 새로운 기사들을 다운로드 할수 있다. 그래서 사용자가 뉴스 애플리케이션을 실행할때 이미 최신 뉴스의 헤드라인과 기사들이 곧바로 제공되게 된다. 백그라운드 fetch 동작의 주기는 시간 간격을 애플리케이션에 지정하여 조절하거나 운영체제에게 맡길수도 있다. 백그라운드 fetch 스케줄을 iOS에게 맡길 경우에는 iOS운영체제가 사용자의 해당 애플리케이션 사용 패턴을 익혀서 fetch 동작의 스케줄링을 하게 된다. 예를 들어 애플리케이션이 매일 점심에 실행된다고 iOS가 알게 되었다면 애플리케이션은 그 시간이 되기 전에 fetch할 기회를 얻게 될것이다.

background fetch는 앞에서 설명했던 Capabilites패널을 통해 애플리케이션의 백그라운드 모드 설정이 활성화되어야 한다. 활성화되었다면 백그라운드 fetch 간격이 설정되어야 한다. 디폴트로, 이것은 fetch 작업이 스케줄링되어 있지 않게 하는 UIApplicationBackgroundFetchIntervalNever로 되어 있다. iOS가 최적화된 fetch 동작을 계산하도록 하려면 다음과 같이 애플리케이션의 setMinimumBackgroundFetchInterval 메서드가 애플리케이션 델리게이트 내에서 호출되어야 한다.


func application(application:UIApplication, didFinishLauchingWithOptionslauchOptions: [NSObject: AnyObject]?) -> Bool{

 application.setMinimumBackgroundFetchInterval(UIApplicationBackgroundFetchIntervalMinimum)
  return true
}

background fetch가 적절하게 활성화되고 설정되었다면 패치할때마다 애플리케이션의 performFetchWithCompletionHandler: 델리게이트 메서드가 호출될것이다. 이 메서드 역시 아래와 같이 애플리케이션 델리게이트 파일 내에 구현되어야 한다

func application(application: UIApplication,performFetchWithCompletionHandler completionHandler:(UIBackgroundFetchResult) -> Void){
print("Background fetch")
completionHandler(UIBackgroundFetchResult.NewData)
}




메서드가 호출될때 이 메서드는 애플리케이션의 최신 콘텐츠(뉴스 앱이라면 뉴스 기사)를 얻기 위한 역할을 한다. 델리게이트 메서드에 인자로 전달되는 것은 fetch가 완료될때 호출되어야 하는 완료 핸들러에 대한 참조체다. 완료 핸들러가 호출되면, fetch 작업이 성공했는지를 가리키는 값이 인자로 전달되어야 한다. 위의 예제에서 완료 핸들러는 새로운 데이터를 받았다는 것을 통보한다. 이 메서드는 fetch가 실패하거나 새로운 데이터가 없을때를 대비한 구현 또한 되어야 한다. 이와 같은 처리는 UIBackgroundFetchResultNoData와 UIBackgroundFetchResultFailed 값을 이용하여 할수 있다.

만약 fetch가 실행되도록 스케줄링되었는데 애플리케이션이 백그라운드에서 실행되고 있지 않다면 운영체제에 의하여 백그라운드에서 실행되고 fetch 델리게이트 메서드가 호출될것이다. 이 동작을 테스트하기 위해 아래와 같이 Xcode의 현재 Scheme을 클릭하여 “Edit Scheme…”메뉴를 선택하여 변경한다.




Background fetch 체크하고
애플리케이션을 다시 실행하면 백그라운드에서 실행되고 fetch delegate 메서드가 호출될것이다.
func application(application: UIApplication,performFetchWithCompletionHandler completionHandler:(UIBackgroundFetchResult) -> Void){
//print("Background fetch")  <- 콘솔창에 출력됨
completionHandler(UIBackgroundFetchResult.NewData)
}

테스트가 끝나면 Scheme을 다시 편집하여 background fetch를 끈다.
두번째 테스트 시나리오는 fetch가 실행될때 애플리케이션이 background에 이미 있는 경우다 이를 위해서 애플리케이션을 실행하고 홈버튼을 이용하여 애플리케이션을 background로 보낸다. background에 갔다면 Xcode의 Debug -> SimulateBackground Fetch 메뉴를 선택한다.

[원격 알림의 개요]
원격 알림(Remote Notification)은 애플리케이션이 원격 서버로부터
알림을 받아 백그라운드에서 수신한 알림을 처리할 수 있게 해준다.
애플리케이션을 위한 알림이 도착하면, 애플리케이션은 깨어나서 그 메세지를 전달받아 그에 대한 동작을 하게 할 수 있다. 일반적으로 알림은
사용자에게 표시되며 애플리케이션에 의해 처리된다.
물론, 알림을 사용자에게 표시하지 않는 옵션을 이용하면 알림을
받은 사실을 애플리케이션만 알도록 할 수도 있다. 예를 들어, 원격서버에서 애필리케이션에 새로운 콘텐츠나 데이터를 다운로드할 수 있다는
알림이 오면 애플리케이션이 그 데이터를 다운로드한 후에 사용자가
애플리케이션을 실행할 때 그 내용이 표시되게 할 수 있다.
프로젝트의 Capabilities 패널에서 원격 알림 백그라운드 모드를 활성화 하고 애플리케이션의 notification 서버를 구축하면, 알림이 디바이스에 도착할 때 애플리케이션의 didReceiveRemoteNotification 델리게이트 메서드가 호출될 것이다. 알림이 처리되면 완료 핸들러가 델리게이트 메서드에 전달된다.


[백그라운드 전송 서비스]

백그라운드 전송 서비스는 배터리 효율을 최적화하는 방법으로 큰 파일의 업로드 또는 다운로드 작업을 백그라운드에서 수행할 수 있게 해준다. 백그라운드 전송 서비스는 애플리케이션이 종료 여부와는 관계없이 지속되며, 디바이스가 다시 부팅될때 자동으로 재개된다. 다운로드가 완료되거나 에러가 발생할 경우, 애플리케이션은 델리게이트 메서드를 통해 알림을 받는다. 전송이 완료되기 전에 애플리케이션을 종료하면, 자동으로 다시 시작된다. 다운로드 세션은 NSURLSessionAPI를 이용하여 구현한다.


[백그라운드 실행 규칙]

애플은 애플리케이션이 백그라운드에서 실행될때 지켜야 될 몇가지 규칙을 권고한다.

– 애플리케이션이 백그라운드일때 작업 수행을 최소화한다. 예를 들면, 음악을 재생하는 애플리케이션의 경우 오직 음악 재생과 관련된 작업만 수행해야 한다. 다른 작업들은 애플리케이션이 포그라운드가 되었을때 처리한다.
– 애플리케이션의 사용자 인터페이스를 갱신하지 않는다. 애플리케이션이 백그라운드에 있으므로 사용자는 사용자 인터페이스 볼수 없다. 따라서 사용자 인터페이스를 갱신해도 아무런 이득을 얻지 못한다. 필요없는 CPU만 사용하고 배터리 소모만 한다.
– OpenGL ES 호출을 하지 않는다. 이를 지키지 않으면 애플리케이션이 강제 종료될수 있다.
– 백그라운드로 진입한다는 알림을 받았을때 상태와 데이터를 저장한다. 일시 중지(Suspended)상태에 있더라도 시스템의 리소스 해제를 위해 애플리케이션이 종료될수 있다.
– 백그라운드 알림을 수신하였을때 주소록, 달력 등 공유 리소스의 사용을 중지한다. 이를 지키지 않으면 애플리케이션이 강제 종료될수 있다.
– 애플리케이션이 foreground로 돌아갈때 영향을 주지 않는 메모리할당은 해제한다. 일시 중지(Suspended) 상태의 애플리케이션이 많은 메모리를 차지하고 있으면 시스템의 메모리 확보를 위해 강제 종료될수 있다.
– Bonjour관련 서비스를 취소한다.

2016년 9월 20일 화요일

App Groups를 이용한 호스트 앱과 Extension 의 데이터 이동 @@ in Swift3 - Xcode 8.0 iOS 10

App Groups 를 이용한 Extension 과 호스트 앱의 데이터 연동

1. FILE -> NEW -> Target 을 눌러 Today Extension 을 추가한다.


Today Extension 을 추가하면 Activate "---" scheme? 라는 문구가 나오는데
그냥 Activate 누르면 된다




이제 프로젝트를 선택해 Capabilities -> App Groups 를 선택해 ON 으로 한 뒤 그룹을 추가해준다


잘못된 앱 그룹 이름은 아래 v 체크된 항목에 오류메세지를 낼 수 있으니  잘 읽고 따라해야 한다.
ex )"group.data.Extension"
이름 지을때 오류가 계속 나타난다면 group.앱이름 을 넣으면 된다.

만들어졌다면 이제 TARGETS -> Extension App 을 선택하여 위와 동일하게
Capabilities -> App Groups 을 ON 으로 변경한다
앱 그룹 리스트 중에 금방전에 만든 앱이름이 뜰 것이다 선택하자!

여기까지 해서 앱과 Extension을 연결 하였다
이제 코딩으로 들어가 데이터 이동이 되도록 해보자
먼저 ViewController에 "name"이라는 키에 값을 넣는 코딩이다
-ViewController
-------------------------------------------------------------------------------------------

 override func viewDidLoad() {

 let shareDefaults = UserDefaults(suiteName: "그룹명")

  shareDefaults!.set("testApp", forKey:"name")
}
===========================================================================

아래코딩은 Extension Widget 에 들어가는 코드이며 저장된 키의 값을 가져온다
-TodayViewController
-------------------------------------------------------------------------------------------
override func viewDidLoad() {  
let shareDefaults = UserDefaults(suiteName: "그룹명")
        let name =  shareDefaults!.object(forKey: "name") as? String

        print(name)
}
===========================================================================
print를 찍어보면 "testApp" 이라는 값이 출력되는것을 확인 할 수 있다
라벨이나 텍스트 필드를 만들어 테스트 해보자


iOS 10 업데이트 정보 링크 모음

 iOS 10 버전이 발표되었습니다
변경사항에 대해서는 정리잘 된 링크 참조하세요

개발자 입장에서 본 iOS 10의 변화점


Siri, 전화, 메시지, 지도까지

2016년 9월 17일 토요일

애드몹 AdMob Swift 화면 하단에 적용 @@ in Swift2.x - Xcode 7.3 iOS 9.3

애드몹 적용하기


import UIKit
import GoogleMobileAds

class ViewController: UIViewController, GADBannerViewDelegate {

  override func viewDidLoad() {
  super.viewDidLoad()

  let bannerView: GADBannerView = GADBannerView(adSize: kGADAdSizeBanner)
  bannerView.frame.origin = CGPointMake(0, self.view.frame.size.height -bannerView.frame.height - 49)
 bannerView.frame.size = CGSizeMake(self.view.frame.width, bannerView.frame.height)

 bannerView.adUnitID = "ca-app-pub-xxxxxxxxxxx/xxxxxxxxx"
 bannerView.delegate = self
 bannerView.rootViewController = self
 let gadRequest:GADRequest = GADRequest()

 // 테스트
 gadRequest.testDevices = [kGADSimulatorID]

 bannerView.loadRequest(gadRequest)
 self.view.addSubview(bannerView)
 }
}

2016년 9월 16일 금요일

Swift 기본 문법 정리 @@ in Swift2.x


Swift 문법 정리

대소문자 구분
let A = 30
let a = 30
이 두 변수는 완전히 서로 다른 변수 이다.

변수
변수는 값을 변경 가능하고 var를 이용해 선언한다.
var i = 1
선언 시 타입을 선언 할 수도 있고
var i : Float = 1.1
var str = "Swift"
대입하는 값으로 타입 선언을 생략할 수 있다
str = "Swift"

상수
상수는 let 으로 선언하고, 한 번 대입된 값을 변경할 수 없다.
let abc = 123
let str = "문자열"

* 변수와 상수명은 [+, _, *, /]와 공백, 첫번째자리에 숫자가 있으면 사용할 수 없고
스위프트에서 사용하는 예약어나 키워드는 변수명으로 사용할수 없습니다.


데이터 타입
-Bool  :  true/false 두가지 종류의 값만 가질 수 있는 자료형으로 논리값 저장시 사용
-Character : 한개 문자 저장
-Int : 양수부터 음수까지의 정수
-Uint(Int8, Int16, Int32, Int64) : Unsigned Integer 줄인 단어로 부호없는 정수 (0 포함)
-Float&Double : 둘다 실수형 값 저장, Float 형은 소수점 아래 7~8 까지 Double 형은 15~16까지
-String : 문자열 저장

문자와 문자열
//문자
let char : character = "a"

//문자열
var str = "abcd"
let str2 = "Swift"

타입이 서로 다른 변수의 결합
String(문자열로 바꾸고 싶은 숫자)

Int(정수로 바꾸고 싶은 문자열)
*숫자로 구성된 문자열만 ex)"123", "345" 등..

배열
// 변경가능
var intArray : [Int] = [ 1, 2, 3, 4, 5 ]

// 변경 불가
let strArray = [ "A", "B", "C" ]

// 개수
intArray.count

// 항목 접근
intArray[2]


딕셔너리
var dic1 : [String:Int] = [ "1월":1, "2월":2, "3월":3]
var dic2 : = ["1월":"January", "2월":"February", "3월":"March" ]
//접근
dic1["2월"]

세미콜론(;)
스위프트 경우 사용하거나 안하거나 상관없다.

2016년 9월 15일 목요일

Camera & PhotoLibrary 접근 @@ in Swift2.x - Xcode 7.3 iOS 9.3


카메라와 카메라롤, 포토라이브러리 접근하는 메소드
- UImagePickerControllerSourceType.Camera : 카메라
. SourceType 속성을 통해 미디어 소스 형태 설정
- UImagePickerControllerSourceType.SavedPhotoAlbum : 포토앨범 사진 저장 
- UImagePickerControllerSourceType.PhotoLibrary : 사진 



func PhotoLibraryOpen (){
  let picker = UIImagePickerController()
  //editing 여부 (사진 편집)
  picker.allowsEditing = true
  picker.delegate = self
  //타입 선언 Camera save Photo Album, PhotoLibrary
  picker.sourceType = .PhotoLibrary
  presentViewController(picker, animated: true, completion: nil)
}



라이브러리 실행 시 가져오는 이미지 타입
- info[UIImagePickerControllerOriginalImage] as UIImage편집되지 않았거나 촬영된 원본 사진은 다음과 같은 방법으로 info 딕셔너리에서 가져오기
- info[UIImagePickerControllerEditedImage] as UIImage편집된 사진 가져오기

@IBOutlet weak var MyImageSet: UIImageView!
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
  var newImage: UIImage
  if let possibleImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
    newImage = possibleImage
    print("EditPhotos")
  } else if 
let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
    newImage = possibleImage
    print("OriginalPhotos")
  } else {
    print("null")
    return
  }
  MyImageSet.contentMode = .ScaleAspectFit
  MyImageSet.image = newImage
  dismissViewControllerAnimated(true, completion: nil)
}

   
라이브러리 View 닫기

func imagePickerControllerDidCancel(picker: UIImagePickerController) {
  dismissViewControllerAnimated(true, completion: nil)
}

2016년 9월 14일 수요일

UICollectionView Cell checkbox 이슈 해결 @@ in Swift2.x - Xcode 7.3 iOS 9.3

이어서 지난 번 CheckBox 코딩의 이슈를 해결코자 한다.
[이슈 내용]
1. 선택된 체크박스가 중복되어 나타난다, 즉 아래로 스크롤 시 체크하지 않았던 체크박스도 체크가 된 상태로 나타나는 경우

2. 스크롤 후 다시 원위치 시 체크박스된 박스가 위치가 바뀌는 현상

일단 두가지 이슈의 공통점은 버튼마다 특정 값을 가지고 있지 않다는 것이다.
다시말해 몇번째 Cell의 Checkbox인지 확인이 어렵다는것이다.

그래서 CheckBox 클래스 값에 변수를 만들어 그 변수에
Cell의  indexPath.row 값을 넣기로 했다
------------------------------------------------------------------------------------------
class CheckBox: UIButton {
    // Images
    
    let checkedImage = UIImage(named: "photoSelect_active")! as UIImage
    let uncheckedImage = UIImage(named: "photoSelect_deactive")! as UIImage

    var tagCount: Int = 0
==========================================================================

------------------------------------------------------------------------------------------
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionCell", forIndexPath: indexPath) as! AlbumPhotoCollectionViewCell
       
        ...
          cell.checkBoxbutton.tagCount = indexPath.row
          
          ...
        return cell

    }
==========================================================================

이렇게 하면 일단 버튼 마다 특정 값을 가지게 할 수 있다.
하지만 이슈처리하는데 가장 풀리지 않았던 문제는 CheckBox 의 중복 되는 문제
하여 CheckBox 클래스 안에서 특정 값을 이용해 Check 표시를 하는 메소드를 만들어보기로 했다.
이렇게 하게 된 가장 큰 이유는 CollectionView 에서 Cell 이 화면에 나타날때마다 객체를 새로 생성
하게 됨을 알게 되었고 생성 될때마다 실행하는 함수를 넣게 될 경우 중복되는 문제를
해결 할 수 있을거라 생각 하였기 때문이였다

일단 tagCount 라는 변수를 이용해 Check 여부를 확인 하는 메소드를 CheckBox 클래스 안에 만들다
------------------------------------------------------------------------------------------
func checkdconform(){
        if GridPhotosHelper.collectionViewCellDic[tagCount] == nil {
            self.isChecked = false
        }else{
           self.isChecked = true
        }
    }
==========================================================================

GridPhotosHelper.collectionViewCellDic[tagCount] 이 값은 체크박스 클릭 시 
tagCount를 key 값으로 딕셔너리로 저장하도록 선언 해놓은 변수이다.
------------------------------------------------------------------------------------------

public struct GridPhotosHelper {

 static var collectionViewCellDic : Dictionary = [Int : Bool]()
...
}
==========================================================================

CheckBox 클래스 내 버튼 클릭 함수에 true, false 값에 따라 딕셔너리로 저장하거나 삭제하도록 코딩을 추가 해준다
------------------------------------------------------------------------------------------
 func buttonClicked(sender:UIButton) {
        print(tagCount)
        if(sender == self){
            if isChecked == true{
                isChecked = false
                GridPhotosHelper.collectionViewCellDic[tagCount] = nil   
            }else{
                isChecked = true
                GridPhotosHelper.collectionViewCellDic[tagCount] = true
            }
        }
    }
==========================================================================
끝으로 객체가 생성 될때마다 위 함수를 실행하도록 코딩을 추가한다
------------------------------------------------------------------------------------------
 func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("CollectionCell", forIndexPath: indexPath) as! AlbumPhotoCollectionViewCell
        
        
          cell.checkBoxbutton.tagCount = indexPath.row
          cell.checkBoxbutton.checkdconform()
          
     ...
        return cell

    }
==========================================================================

앱을 테스트 해보면 이슈들이 해결된것을 확인 할 수 있고 체크된 Checkbox 의  값들도
딕셔너리에 따로 저장된다.

2016년 9월 13일 화요일

UICollectionView Cell checkbox 사용 @@ in Swift2.x - Xcode 7.3 iOS 9.3

오늘은 UICollectionView Cell 마다 Checkbox 기능을 추가 할 예정이다.
일단 Checkbox를 넣는 이유는 Poster라이브러리에서 자신이 선택하고 싶은 사진들을 표시하기
위해서이다.

내가 가지고 있는 교재에선 참고할만한 예제가 없어 구글링을 하여 코딩했다.

무슨 이유인지 아직 모르지만 아래코딩은 Checkbox가 중복체크가 된다.(첫 화면에 Checkbox를 체크 하고 스크롤 하여 아래로 내리면 체크하지 않았던 Checkbox에 체크 표시가되어있는 문제가 발생)

그래도 일단 코딩을 정리하고 이슈를 해결 할 예정이다.
----------------------------------------------------------------------------------------------
class CheckBox: UIButton {
    // Images
    
    let checkedImage = UIImage(named: "photoSelect_active.png")! as UIImage
    let uncheckedImage = UIImage(named: "photoSelect_deactive.png")! as UIImage
    
    // Bool property
    var isChecked: Bool = false {
        didSet{
            if isChecked == true {
                self.setImage(checkedImage, forState: .Normal)
            } else if isChecked == false {
                 print("test2!!")
                self.setImage(uncheckedImage, forState: .Normal)
            }
        }
    }
    override func awakeFromNib() {
        self.addTarget(self, action: #selector(CheckBox.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
        self.isChecked = false
    }
    
    func buttonClicked(sender:UIButton) {
        if(sender == self){
            if isChecked == true{
                isChecked = false
               
            }else{
                isChecked = true
               
            }
        }
    }

}
=============================================================================
위 코딩은 구글링을 통해서 체크버튼 클릭 마다 isChecked 값에 따라 이미지를 바꿀수 있도록 한다.
스토리보드 버튼에 위 클래스 값을 넣었다. 참고로 버튼 타입을 "Custom"으로 해주어야만 
이미지가 제대로 나온다.

체크 이미지와 미체크 이미지를 설정 해주고
----------------------------------------------------------------------------------------------
    let checkedImage = UIImage(named: "photoSelect_active")! as UIImage
    let uncheckedImage = UIImage(named: "photoSelect_deactive")! as UIImage
=============================================================================
속성 값에 따른 이미지 배치
----------------------------------------------------------------------------------------------
 // Bool property
    var isChecked: Bool = false {
        didSet{
            if isChecked == true {
                self.setImage(checkedImage, forState: .Normal)
            } else if isChecked == false {
                self.setImage(uncheckedImage, forState: .Normal)
            }
        }
=============================================================================

클래스 시작 시 처음 실행되는 함수 버튼에 터치 함수를 넣어주고 버튼의 속성값을 false 로 설정
----------------------------------------------------------------------------------------------
 // Bool property
override func awakeFromNib() {
        self.addTarget(self, action: #selector(CheckBox.buttonClicked(_:)), forControlEvents: UIControlEvents.TouchUpInside)
        self.isChecked = false
    }
=============================================================================
체크버튼에 들어간 클릭 함수, 클릭마다 속성이 바뀐다
----------------------------------------------------------------------------------------------
 func buttonClicked(sender:UIButton) {
        if(sender == self){
            if isChecked == true{
                isChecked = false
            }else{
                isChecked = true
            }
        }
    }
=============================================================================
스토리보드의 버튼의 클래스 값에 적용 후 테스트를 해보면 
체크버튼이 클릭 될때마다 이미지가 바뀌는것을 확인 할 수 있으나 이슈가 발생한다.

[이슈 정리]
1. 선택된 체크박스가 중복되어 나타난다, 즉 아래로 스크롤 시 체크하지 않았던 체크박스도 체크가 된 상태로 나타나는 경우

2. 스크롤 후 다시 원위치 시 체크박스된 박스가 위치가 바뀌는 현상



2016년 9월 12일 월요일

메모리 체크 ( instruments memory) @@ in Swift2.x - Xcode 7.3 iOS 9.3

지난번 앱 이슈를 확인하기 위해 메모리체크를 하였다.
메모리체크하는 방법을 정리 해볼까 한다.

1.xcode  프로젝트 빌드  후 디버깅 탭 선택
* 빌드를 하지 않았을 경우 아래처럼 디버깅 탭을 눌러도 아무것도 없음 “ No Debug Session” 만 뜸




2. 디버깅 탭에서 Memory 를 선택 하면 우측 화면에 Memory 상태를 나타내는 화면이 뜸
   우측 사단의 Profile in Instruments 클릭


3. Profile in Instruments 을 클릭 한 후 메세지 창이 아래처럼 뜸
   그럼 “Restart” 버튼을 클릭
*Transfer를 클릭 해도 되지만 어떤사람이 저걸 눌렀다가 데이터가 손실되는 일이 발생하였다함




4. 제대로 했다면 앱이 실행되면서 Instruments 가 실행 되면 필터 창에 앱 이름 서치 아래그림 참조
*우측 하단을 보면 옵션설정이 있으나 나중에 블로그참조!! (http://blog.canapio.com/44 여기참조)



5. 이제 메모리 상태가 나온다





6. 그 외 에도 앱 상태를 확인 할 수 있는 방법이 여러가지이다
자세히 알아보려면 구글링하도록 하자!!
오른쪽 상단의 "+" 를 누르면 여러가지 아이콘이 나오는데 한번씩 보면 좋을듯 하다.

2016년 9월 10일 토요일

in-app purchases (Restore 추가) @@ in Swift2.x - Xcode 7.3 iOS 9.3

in-app purchases 적용하기 

앱 내 결제기능을 적용하기위해  in-app purchases를 사용한다.

필요조건
 -  itunes connect 계정 (은행정보 입력되어 있어야 함)


1. 프레임 워크 추가하기

프레임워크는 좌측 프로젝트 > TARGETS > Capabilities > in-App Purchase  ON 으로 하여 완료되었을경우  자동으로 추가됨 




프레임워크 추가 되었는지 확인
프로젝트  > General 하단에 > Libraries > StoreKit.framework 확인

2. 인 앱 제픔 productID 확인하기

 ViewController.swift 

// StoreKit 를 import 해주고 SKProductsRequestDelegate,SKPaymentTransactionObserver 를 추가 합니다
import UIKit
import StoreKit 



class ViewController: UIViewController,SKProductsRequestDelegate,SKPaymentTransactionObserver {
         ...
    }

// productID 에는 itunes connect > 나의 App > CustomApp > App내 추가기능 에 등록 한 제품 ID를 입력 합니다. ( 미리 제품을 등록해야 합니다!)
    

UIViewController,SKProductsRequestDelegate,SKPaymentTransactionObserver {

    // 제품 타이틀과 정보, 구매버튼 아울렛
    @IBOutlet weak var productTitle: UILabel!
    @IBOutlet weak var productDescription: UITextView!
    @IBOutlet weak var buybutton: UIButton!
    @IBOutlet weak var reStorebutton: UIButton!
         var product: SKProduct?


         var productID = "product ID 입력"
                   ...
    }




3. 코딩 작업

이제 결제 작업을 위해 트랜젝션 감시자 설정과, 제품정보를 얻어서 사용자에게 표시하는 메서드
를 만들어야 한다. 

  override func viewDidLoad() {
        super.viewDidLoad()
       //구매제품정보 얻을때까지 구매버튼, Restore 버튼 비활성화

        buybutton.enabled = false
        reStorebutton.enabled = false 
       //결제 작업을 위해 트랜잭션 감시자를 설정
        SKPaymentQueue.defaultQueue().addTransactionObserver(self)
       //앱스토어에 접속하고 지정한 ID에 대한 제품 정보를 가지고 와서 표시하는 함수 호출
        ItemProduct()
}
  // 제품정보가져오는 함수
  func ItemProduct(){
   if SKPaymentQueue.canMakePayments(){
   let request = SKProductsRequest(productIdentifiers: NSSet(object: self.productIDasSet<String>)
            request.delegate = self

           // 제품정보를 가져올 경우 didReceiveResponse 델리게이트 메서드 호출
            request.start()
        }else{
            print("Please enable in App purchase in Settings")
        }
    }


  //didReceiveResponse 델리게이트 메서드
 func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
        var products = response.products
        print(products)
        if (products.count != 0){
            product = products[0as SKProduct
            //제품정보 확인! 구매버튼, Restore 버튼 활성화
            buybutton.enabled = true
            reStorebutton. enabled = true        
            // 제품 타이틀과 정보 각각 넣기
            productTitle.text = product!.localizedTitle
            productDescription.text = product!.localizedDescription
        }else{
            print("product not found")
        }
        if response.invalidProductIdentifiers.count != 0 {
            print(response.invalidProductIdentifiers.description)
        }
        for product in products
        {
            print("Product not found: \(product)")
        }   
    }
  // 구매버튼의 함수에 결제프로세스를 시작트랜잭션 감시자 객체의 updatedTransactions 메서드를 호출하도록 한다

   @IBAction func buyproduct(sender: AnyObject) { 
        let payment = SKPayment(product: product!)
        SKPaymentQueue.defaultQueue().addPayment(payment)
    }   
// Restore 버튼 함수에 Restore 트랜젝션 메서드 호출
  @IBAction func RestoreAction(sender: AnyObject) {
        print("restore")
        SKPaymentQueue.defaultQueue().addTransactionObserver(self)
        SKPaymentQueue.defaultQueue().restoreCompletedTransactions()

    }


 //updatedTransactions 메서드
    func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transactions in transactions as [SKPaymentTransaction]{
             switch transactions.transactionState{
            case SKPaymentTransactionState.Purchased:
                print("buy, ok unlock")
                self.success()
                SKPaymentQueue.defaultQueue().finishTransaction(transactions)
                break;
            case SKPaymentTransactionState.Failed:
                print("buy error")
                SKPaymentQueue.defaultQueue().finishTransaction(transactions)
                break;
            case SKPaymentTransactionState.Restored:
                print("Rstore")
                self.restoreFeature()
                SKPaymentQueue.defaultQueue().finishTransaction(transactions)
                break;
            default:
                print("defult")
                break;
            }
        }
    }
    
  // 결제 완료 됐을 실행될 함수
  func success() {
        buybutton.enabled = false
        print("ok")
    }
  // Restore 완료 됐을 시 실행될 함수
  func restoreFeature(){
        reStorebutton.enabled = false
        print("restore")

    }


이렇게 구매버튼 과 Restore 버튼을 사용하여 인 앱 결제를 마무리 하였다.

추천 게시물

애플 개발자 등록방법 2016년 5월 8일 기준!!

애플 개발자 등록 절차 1. 개발자 등록 페이지 이동    애플 개발자 로그인 > Account 페이지 이동 > 하단 영역 클릭 (이미지 참조)   >> Enroll 클릭 >> 무조건 승인!! ...