Programming/iOS2016.10.25 23:46

앱을 실행해면 전면 광고를 실행해서 보여주게 하였다. 


그러던 중 admob 으로 부터 메일 한통을 받았다.



경고를 받은 앱은 Android 앱이었지만 iOS에서도 동일한 방식으로 나오고 있었다. 


iOS도 수정이 필요한 상황이었다. 그래서 정책을 알아 보기로 했다. 여러 가지 정책이 있어서 모든 것을 다 소개할 수도 없고 내가 필요한 것은 기존 상태를 유지하면서 고칠 수 있는 방법이었다.


https://support.google.com/admob/answer/6201362?hl=ko&ref_topic=2745287


`예기치 않게 실행되는 삽입 광고` > `허용되지 않는 광고 구현의 예: 앱을 열 때 삽입 광고 실행` 


이 경우가 경고를 받았을 때의 경우이다. 


이걸 이제 어떻게 수정해야 할까..



이 방법대로 해보자~!!!


iOS Launch screen 이 나오고 메인 화면이 나오기 전에 광고가 떠야 한다.


그렇다면... AppDelegate 의 didFinishLaunchingWithOptions 에서 띄우면 될것 같아!


1. AppDelegate 에서 전면광고 띄우기


import GoogleMobileAds

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GADInterstitialDelegate {

    var window: UIWindow?
    var interstitial: GADInterstitial?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        interstitial = GADInterstitial(adUnitID: "********")

        let request = GADRequest()
        // Requests test ads on test devices.
        interstitial?.load(request)

        return true
    }

    func interstitialDidReceiveAd(_ ad: GADInterstitial!){
        guard let viewController = window?.rootViewController else { return }
        ad.present(fromRootViewController: viewController)
    }
}

앱 실행시 광고를 띄우는 코드는 모두 작성되었다. 이제 실행을 해보자~!



음...? 메인화면이 먼저 뜨고 전면광고가 뜬다. 위에 안 되는 경우가 바로 이런 경우이다.


이렇게 하면 정지를 받을게 뻔하다... 이건 아님...



전면광고를 로딩하는 시간 때문에 어쩔 수 없이 메인 화면이 노출되는 것 같다. 그러면... 만족스럽지 못하지만 자주 쓰는 방법으로 페이크 LaunchScreen 을 이용해 보자. 


Launch screen 과 같은 VC를 바로 띄워서 전면고를 보여주면...? 문제가.. 없겠는데?!! 그리고 전면광고를 닫을때 메인 화면으로 이동해주자.


2. Fake launch screen 을 이용하여 전면광고 띄우기


import GoogleMobileAds

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GADInterstitialDelegate {

    var window: UIWindow?
    var interstitial: GADInterstitial?


    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        if let viewController = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController() {
            window?.rootViewController = viewController
        }

        interstitial = GADInterstitial(adUnitID: "******")
        interstitial?.delegate = self

        let request = GADRequest()
        interstitial?.load(request)

        return true
    }

    func interstitialDidReceiveAd(_ ad: GADInterstitial!){
        guard let viewController = window?.rootViewController else { return }
        ad.present(fromRootViewController: viewController)
    }

    func interstitialWillDismissScreen(_ ad: GADInterstitial!) {
        if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() {
            window?.rootViewController = viewController
        }
    }
}

이렇게 하면 완벽히 동작할 것이다. 이제 전면광고로 부터 자유로워 질 것이다.


실행을 해보자. 밑에 동영상을 확인해 보자.




우리가 원하는 동작이다!! 와우~!!


그런데 뭔가가... 좀... 부족하다..


전면 광고가 사라지는 애니메이션이 사라졌다. willDismiss에서 rootViewController를 변경해서 그런가 보다..


그렇다고 didDismiss 에서 rootViewController 를 변경하면 애니메이션이 끝나기 전까지 전면광고가 보인다. 이렇게 하면 다음과 같은 경우로 허용되지 않는다.



Launch screen에서 광고를 띄워야 하는 것은 유지되어야 하고 광고가 사라지는 애니메이션이 유지되면서 메인화면이 보여야 한다. 


일단 window의 rootViewController를 변경하면 애니메이션을 유지하지 못한다. rootViewController를 변경하는 방법을 제외한다.


그럼... Launch screen 을 VC 형태로 하는게 아니라 view 로 보여주는건 어떨까? view면 바로 추가하고 제거하면 되지 않을까?


음? 그럴듯하다. 한번 해보자. 생각하면 답이 안 나온다. 일단 삽질하다 보면 답이 나오겠지. 어서 해보자.


import GoogleMobileAds

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GADInterstitialDelegate {

    var window: UIWindow?
    var interstitial: GADInterstitial?
    var launchScreenView: UIView?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.

        if let view = UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()?.view {
            launchScreenView = view
            view.translatesAutoresizingMaskIntoConstraints = false
            
            if let rootView = window?.rootViewController?.view {
                rootView.addSubview(view)
                
                var constraints = [NSLayoutConstraint]()
                constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[view]-0-|", options: [], metrics: nil, views: ["view": view])
                constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[view]-0-|", options: [], metrics: nil, views: ["view": view])
                rootView.addConstraints(constraints)
            }
        }

        interstitial = GADInterstitial(adUnitID: "******")
        interstitial?.delegate = self

        let request = GADRequest()
        interstitial?.load(request)

        return true
    }

    func interstitialDidReceiveAd(_ ad: GADInterstitial!){
        guard let viewController = window?.rootViewController else { return }
        ad.present(fromRootViewController: viewController)
    }

    func interstitialWillDismissScreen(_ ad: GADInterstitial!) {
        launchScreenView?.removeFromSuperview()
    }
}


후후 코드는 완벽하다.


이제 실행 동영상을 보자.





완벽하게 내가 원했던 동작이다.


축하~ 축하!!



여러분의 광고 수익에 조금이나마 도움이 되었으면 좋겠다.





신고
Posted by 초프(초보 프로그래머)
Programming/iOS2016.10.21 00:41

Swift build를 하다가 컴파일이 끝나지 않고 계속되는 현상을 발견했다.





Appcode로 build 중이었는데 swift의 메모리가 끝없이 계속 증가했다.


의심가는 코드를 제거해 보니 빌드가 잘 된다.


아.. 엄청나다.


의심가는 코드를 Playground로 재작성해보았다.


?? 를 남발해서 그런것으로 보이는데... 이래도 되는건가?

let optionalString: String? = nil

let abc: [String:Int] = [
    "a": Int(optionalString ?? "") ?? 0,
    "B": Int(optionalString ?? "") ?? 0,
    "c": Int(optionalString ?? "") ?? 0,
    "d": Int(optionalString ?? "") ?? 0,
    "e": Int(optionalString ?? "") ?? 0
]
print(abc)


관련링크 : http://stackoverflow.com/questions/26151954/sourcekitservice-consumes-cpu-and-grinds-xcode-to-a-halt

신고
Posted by 초프(초보 프로그래머)
TAG Build, swift
Programming/iOS2016.06.22 00:51

ENABLE_BITCODE 설정때문에 발생하는 오류입니다.


coocapods를 사용할 경우 pods안의 프로젝트들의 ENABLE_BITCODE가 true로 설정되어 발생합니다.


podfile 안에 다음 코드를 추가하면 해결됩니다.


post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['ENABLE_BITCODE'] = 'NO'
    end
  end
end

신고
Posted by 초프(초보 프로그래머)
Programming/iOS2016.03.24 17:14

Extension 사용하는 앱을 upload to app store 할때  90205, 90206 에러가 동시에 발생하는 경우

cocoapods가 원인..


1. Open the (Your App).xcodeproj file (this is the first file on the project navigator pane).
2. Switch to the target for your app extension (on the top left of the middle pane).
3. Go to the Build Phases tab
4. Click the X after "Embed Pod Frameworks"

https://github.com/CocoaPods/CocoaPods/issues/4203

신고
Posted by 초프(초보 프로그래머)
Programming/iOS2016.02.12 11:30

XCode, AppCode 등에서 iOS 앱 개발을 하다가 새로운 오류를 경험했습니다.

시뮬레이터에서 잘 되는데 실제 디바이스에서는 실행이 되지 않고 다음과 같은 오류가 발생했습니다.

AppCode

failed installing application, reason: Error Domain=JBDeviceKitErrprDomain Code=-402620392 "AMDeviceSecureUpgradeApplication failed with err = -402620392(The identity used to sign the executable is no longer valid.)" UserInfo={NSLocalizedDescription=AMDeviceSecureUpgradeApplication failed with err = -402620392(The identity used to sign the executable is no longer valid.)}. Reason: AMDeviceSecureUpgradeApplication failed with err = -402620392(The identity used to sign the executable is no longer valid.)


Xcode

The identity used to sign the executable is no longer valid.
Please verify that your device's clock is properly set, and that your signing certificate is not expired. (0xE8008018).


Device의 시간은 자동설정으로 되어 있었고 현재 시간을 표시하고 있었습니다.

Derived Data를 삭제하고 clean build 를 해봐도 같은 오류만 나올 뿐입니다.

실행 중에 발생한 오류라 인증서 문제를 생각해 볼 수 있는데요. '키체인 접근' 앱에서 확인해 보니 1년뒤 만료였습니다.

그럼 왜 안될까요....


'키체인 접근' 에서 인증서를 자세히 보니 만료된 인증서가 몇개 있었습니다. 같은 Developer의 인증서인데 만료된것과 유효한 것이 같이 있었죠.

만료된 것을 모두 지웠습니다. 그리고 XCode에서 실행하니 인증서 문제가 있다고 fix it 이 떴습니다.

fix 한 후에 실행해 보니 정상 실행됩니다~!


디바이스 실행이 안되는 오류가 있을 때에는 항상 인증서를 확인하자~!

만료된 인증서는 지워버리자~!

신고
Posted by 초프(초보 프로그래머)
Programming/iOS2015.10.06 16:54



Action Extension Target을 추가하여 이전까지 문제 없이 사용하고 있었다.

오늘 업로드를 하는 과정에서 다음과 같은 오류가 발생하면서 더 진행이 되지 않는다......

검색과 삽질 끝에 해결은 했다.

Extension 자체는 Framework를 copy하지 않기 때문에 App이 가지고 있는 Framework를 사용해야 한다고 한다.

cocoapods로 Extension Target에 framework를 추가했었는데 이 부분이 문제였다.

이전 까지 문제 없다가 iOS9 출시 후 부터가 문제가 되기 시작했다.

Extension Target에서 cocoapods의 흔적을 다 지우고 다음 링크에 있는 방법대로 하면 된다.

https://hanseihee.wordpress.com/2015/01/14/xcode-cocoapods-with-today-extension/


---- 수정 ---

https://github.com/CocoaPods/CocoaPods/issues/4203

신고
Posted by 초프(초보 프로그래머)
Programming/iOS2015.08.28 01:31

cocoapods 0.38.2 로 업데이트 후에 pod 명령어만 실행하면 아래와 같은 오류가 발생한다.


Chopeui-MacBook-Pro:iOS-classic Chope$ pod --version

---------------------------------------------
Error loading the plugin with path `/Library/Ruby/Gems/2.0.0/gems/cocoapods-stats-0.6.0/lib/cocoapods_plugin.rb`.

LoadError - cannot load such file -- /Library/Ruby/Gems/2.0.0/gems/cocoapods-stats-0.6.0/lib/cocoapods_plugin.rb
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:126:in `require'
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb:126:in `require'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command/plugin_manager.rb:106:in `safe_require'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command/plugin_manager.rb:32:in `block in load_plugins'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command/plugin_manager.rb:31:in `each'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command/plugin_manager.rb:31:in `load_plugins'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command.rb:304:in `block in run'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command.rb:303:in `each'
/Library/Ruby/Gems/2.0.0/gems/claide-0.9.1/lib/claide/command.rb:303:in `run'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-0.38.2/lib/cocoapods/command.rb:48:in `run'
/Library/Ruby/Gems/2.0.0/gems/cocoapods-0.38.2/bin/pod:44:in `'
/usr/bin/pod:23:in `load'
/usr/bin/pod:23:in `
' --------------------------------------------- 0.38.2

검색해보니 rvm을 업데이트하면 된다고 한다.

\curl -sSL https://get.rvm.io | bash -s stable --ruby

잘 안되서... ruby 버전을 2.2.1로 업데이트 하고 다시 rvm 업데이트를 시도 했다.

pod 명령어는 오류 없이 잘 동작함!

신고
Posted by 초프(초보 프로그래머)
TAG cocoapods, ios, POD
Programming/iOS2015.07.24 00:21
Swift version : 1.2
iOS version : 8.4


Swift 문법도 익숙치 않은데 NSNotificationCenter 사용하려는데 막히는 부분이 많고...

Notificaiton 에서 keyboard 관련 정보 가져오려는데.... 왜이리 오류가 많은지..

다음과 코드가 동작함을 확인하였으니 참고하세요.


 NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardWillShow:"), name: UIKeyboardWillShowNotification, object: nil)


    func keyboardWillShow(notification: NSNotification) {
        let userInfo = notification.userInfo!

        let beginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
        let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let animationOptions = UIViewAnimationOptions((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).unsignedLongValue << 16)

        UIView.animateWithDuration(duration, delay: 0.0, options: animationOptions, animations: { () -> Void in
            
        }, completion: nil)
    }


Swift version : 2.0
iOS version : 9.0


animationOptions 를 구하는 방법이 변경되었습니다.


    func keyboardWillShow(notification: NSNotification) {
        let userInfo = notification.userInfo!

        let beginFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as! NSValue).CGRectValue()
        let endFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
        let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
        let animationOptions = UIViewAnimationOptions(rawValue: (userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).unsignedLongValue)

        UIView.animateWithDuration(duration, delay: 0.0, options: animationOptions, animations: { () -> Void in
            
        }, completion: nil)
    }


신고
Posted by 초프(초보 프로그래머)
TAG ios, swift
Programming/iOS2015.07.16 02:05

ReactiveCocoa 교육을 듣고 잊기 전에 예제를 만들었습니다.

시나리오

  • 로그인
    • id, pw 값이 있으면 Login 버튼이 활성화
    • id, pw 값 중 하나라도 없으면 login 버튼은 비활성화
  • 카운터
    • 0.5 초로 반복하여 count
    • 0.5 초 마다 red 가 show/hide를 반복
    • 2.5 초 마다 blue가 show/hide를 반복
    • 10 초 후에 timer 종료
    • red, blue 를 green으로 표시


해당 예제 커밋


설명은 추후에.... 


신고
Posted by 초프(초보 프로그래머)
Programming/iOS2015.06.23 11:41

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'Could not load NIB in bundle: 'NSBundle  (loaded)' 
with name 'viewController' and directory 'Main.storyboardc''


Storyboard를 사용하고 있는데 지금까지 없던 위와 같은 오류가 발생했네요.

폰에서는 문제가 없는데 iPad2 7.1 시뮬레이터에서만 위와 같은 오류가 발생합니다. 

Storyboard를 수정한 것과 관련된 것 같은데 정확한 이유는 알 수 없지만.... 해결 방법은 간단합니다.


Storyboard가 빌드된 storyboardc가 없어지도록 캐시를 비우면 된다고 하는데.....

몰라.. 뭔가 복잡해..


더 간단한 방법은 Storyboard 이름을 리펙하시면 됩니다.

그럼 해결~

신고
Posted by 초프(초보 프로그래머)