Xcodeをチマチマと。

Xcodeビギナーによるブログです。

エラーコード ITMS-90788

Incomplete Document Type Configuration. ということだそうです。

アプリのアーカイブをAppStoreConnectにアップロードしようとした際にOrganizerウィンドウで発生したエラー。

解決策:

info.plistファイルのDocument types に登録してあるItemのところに、すでにCFBundleTypeIconFilesとDocument Type Name があるところに、

(1) Role

(2) Handler rank

を追加する。

(1) Role はType:String、Valueここを参照。

(2) Handler rank はType:String、Valueここを参照。

で、解決しました。

coredataを追加する

AppleWatch対応の新規プロジェクトを作成すると、use coredataのチェックがない。そこで、あとでcoredataをあとで追加する方法のメモ。

目標:プロジェクトにcoredataを追加する

1. xcdatamodeldファイルを追加

2. AppDelegateファイルにコードを追加

簡単!

1. xcdatamodeldファイルを追加

xcodeメニューのFile>New>File...>iOS>DataModel を選択しNextで「Model.xcdatamodeled」で追加。

2. AppDelegateファイルにコードを追加

import CoreData

と、

// MARK: - Core Data stack

    

    lazy var applicationDocumentsDirectory: NSURL = {

        let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)

        return urls[urls.count-1] as NSURL

    }()

    

    lazy var managedObjectModel: NSManagedObjectModel = {

        let modelURL = Bundle.main.url(forResource: "Model", withExtension: "momd")

        return NSManagedObjectModel(contentsOf: modelURL!)!

    }()

    

    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {

        var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)

        let url = self.applicationDocumentsDirectory.appendingPathComponent("Model.sqlite")

        var error: NSError? = nil

        var failureReason = "There was an error creating or loading the application's saved data."

        do {

            try coordinator!.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: url, options: nil)

        } catch var error1 as NSError {

            error = error1

            coordinator = nil

            abort()

        } catch {

            fatalError()

        }

        

        return coordinator

    }()

    

    lazy var managedObjectContext: NSManagedObjectContext? = {

        let coordinator = self.persistentStoreCoordinator

        if coordinator == nil {

            return nil

        }

        var managedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

        managedObjectContext.persistentStoreCoordinator = coordinator

        return managedObjectContext

    }()

    // MARK: - Core Data Saving support

 

    func saveContext () {

        let context = managedObjectContext!

        if context.hasChanges {

            do {

                try context.save()

            } catch {

                let nserror = error as NSError

                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")

            }

        }

    }

を追加する。

あとは、いつも通りxcoredatamodeldファイルを編集していく。

 

iPhoneとAppleWatchの値渡し

目標:iPhoneで選択した値をAppleWatchに表示する、および、AppleWatchで選択した値をiPhoneに表示する。(※ Simulator)

通信できいない状態のときに変更されるかも知れないので、タイムスタンプ(String)を用いて最新の選択を表示できるようにもする。通信の種類が不適切なものもあるが、仕様をシンプルにするため、1種類のみにした。

  1. AppleWatch Simulator(以下AppleWatch)をiPhone Simulator(以下iPhone)をペアリング
  2. iPhoneとAppleWatchの値渡しの概要
  3. iPhone側のStoryBoardとViewControllerの修正
  4. AppleWatch側のInterfaceとInterfaceControllerの修正

まずは、AppleWatchアプリ作成のプロジェクトを新規作成する。

f:id:JoeCola:20180805114714p:plain

 1. AppleWatch Simulator(以下Watch)をiPhone Simulator(以下iPhone)をペアリング

XCodeの Window > Devices and Simulators で右ペインの PAIRED WATCHES で+ボタンを押してペアリングさせる。

f:id:JoeCola:20180805115445p:plain

 2. iPhoneとAppleWatchの値渡しの概要

値を送りたいタイミングで、次図の上にあるメソッドを使用する。

すぐに値を送るものと、機を見ていいタイミングで値を送るものとがある。

参照頁。このページは大変良い。

それで、次図の下にあるメソッドを構えておくと、値を受け取ることができる。

逆の送受信も同じ。

f:id:JoeCola:20180805140245p:plain

 3. iPhone側のMainStoryBoardとViewControllerの修正

①MainStoryBoardの修正

次のように設定。

・TextField配置。ViewControllerにIBOutletを引っ張る。

・ButtonのA〜Cを配置。すべてのButtonをVirewControllerの同じActionに引っ張る。

f:id:JoeCola:20180805151709p:plain

② ViewControllerの修正

とりあえず全文。

import UIKit

import WatchConnectivity // (A)

 

class ViewController: UIViewController ,WCSessionDelegate { // (B)

    var context: [String: Any]? = nil // (C)

    @IBOutlet weak var field: UITextField!

    @IBAction func touchButton(_ sender: UIButton) {

        let ABC = sender.titleLabel?.text // (D)

        let newContext = ["TIMESTAMP": TimeStamp, "ABC": ABC!]

        context = newContext

        UserDefaults.standard.set(newContext, forKey: "CONTEXT") // (E)

        field.text = ABC!

        if WCSession.isSupported(){

            try? WCSession.default.updateApplicationContext(newContext) // (F)

        }

    }

    override func viewDidLoad() {

        super.viewDidLoad()

        let defaults = UserDefaults.standard

        let defaultContext = defaults.dictionary(forKey: "CONTEXT")

        if defaultContext == nil {// (G)

            let newContext = ["TIMESTAMP": TimeStamp, "ABC": "A"]

            defaults.set(newContext, forKey: "CONTEXT")

        }

        if context == nil{// (H)

            self.setContext(UserDefaults.standard.value(forKey: "CONTEXT") as! [String : Any])

        }

        if WCSession.isSupported(){// (I)

            let session = WCSession.default

            session.delegate = self

            session.activate()

        }

    }

    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

    }

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { // (J)

        print("ViewController_SessionactivationDidCompleteWith:")

        print(activationState)

    }

    func sessionDidBecomeInactive(_ session: WCSession) { // (K)

        print("ViewController_SessionDidBecomeInactive:")

    }

    func sessionDidDeactivate(_ session: WCSession) { // (L)

        print("ViewController_SessionDidDeactivate:")

    }

    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) {

        if context == nil { // (M)

            self.setContext(applicationContext)

        }else {

            let timeStamp = context!["TIMESTAMP"] as! String

            let applicationTimeStamp = applicationContext["TIMESTAMP"] as! String

            if timeStamp < applicationTimeStamp {

                self.setContext(applicationContext)

            }else if timeStamp > applicationTimeStamp {

                try? session.updateApplicationContext(context!)

            }

        }

    }

    func setContext(_ context:[String: Any]){ // (N)

        self.context = context

        let ABC = context["ABC"] as! String

        DispatchQueue.main.async { // (O)

            self.field.text = ABC

        }

        UserDefaults.standard.set(context, forKey: "CONTEXT")

    }

    var TimeStamp: String { // (P)

        let date = Date()

        let formatter = DateFormatter()

        formatter.dateFormat = "yyyyMMddHHmmss"

        return formatter.string(from: date)

    }

}

(A) iPhoneとの通信のためのフレームワーク

(B) WCSessionクラスのデリゲートを設定。

(C) データを保持するためのプロパティ。

(D) ボタンがタップされたとき、ボタンのタイトルを取得し分岐させる。

(E) データの永続保持のためにUserDefaultsを使用。

(F) AppleWatchに送信。

(G) UserDefaultsにデータがなかったときに初期化。

(H) プロパティに値がセットされていないとき、UserDefaultsから値取得。

(I) iPhoneとの通信がサポートされているかどうか確認し、サポートされていれば、デリゲートをセットし、通信をアクティブにする。

(J)〜(L) WCSessionDelegateの必須メソッド。

(M) WatchからのupdateApplicationContext( )を受け取るメソッド。送られてきたcontextのTimestampを調べ、Watch側の方が新しければ更新、iPhone側が新しければ送り返すように設定。

(N) contextを元に、プロパティとラベルに値を流し込むメソッド。

(O) UIViewの書き換えはメインスレッドでしかできない。衝突が起こらないように注意して使用する。

(P)  現在時刻からタイムスタンプ文字列を返す。

 

 4. AppleWatch側のInterfaceStoryBoardとInterfaceControllerの修正

① RowControllerファイルの作成

UIKitで言うところのUITableViewCellのようなファイル。NSObjectのサブクラス。

File > New > File > WatchKit Class

クラス名はTableRowController、ターゲットがWatchKitExtensionになっていることを確認し作成する。

② InterfaceStoryBoardの修正

次のように設定。

・「TEST_WATCH_2」はLabel。InterfaceControllerにIBOutletを引っ張る。

・「Label」はTableをまず置いて、その上に置いたLabel。TableRowControllerにIBOutletを引っ張る。

・TableのRowControllerのクラスをTableRowControllerにセットし、IdentifierをTableRowにセット。

f:id:JoeCola:20180805141450p:plain

② InterfaceControllerの修正

 とりあえず、全文。

 

import WatchKit

import Foundation

import WatchConnectivity // (A)

 

class InterfaceController: WKInterfaceController, WCSessionDelegate { // (B)

    @IBOutlet var table: WKInterfaceTable!

    @IBOutlet var field: WKInterfaceLabel!

    var items = ["A","B","C"] // (C)

    var context: [String: Any]? = nil // (D)

    override func awake(withContext context: Any?) {

        super.awake(withContext: context)

        table.setNumberOfRows(items.count, withRowType: "TableRow") //(E)

        for (index, item) in items.enumerated(){

            let controller = table.rowController(at: index) as! TableRowController

            controller.label.setText(item)

        }

        if WCSession.isSupported(){ // (F)

            let session = WCSession.default

            session.delegate = self

            session.activate()

        }

    }

    override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) { // (G)

        let newContext = ["TIMESTAMP": TimeStamp, "ABC": items[rowIndex]]

        self.setContext(newContext)

        if WCSession.isSupported(){

            try? WCSession.default.updateApplicationContext(newContext)

        }

    }

    override func willActivate() {

        super.willActivate()

        if context == nil {

            if WCSession.isSupported(){

                let dammyContext = ["TIMESTAMP": "0", "ABC": ""] // (H)

                try? WCSession.default.updateApplicationContext(dammyContext)

            }else {

                let newContext = ["TIMESTAMP": TimeStamp, "ABC": "A"]

                setContext(newContext)

            }

        }

    }

    override func didDeactivate() {

        super.didDeactivate()

    }

    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { // (I)

        print("InterfaceController_activationDidCompleteWith:")

        print(activationState)

    }

    func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String : Any]) { // (J)

        if context == nil {

            self.setContext(applicationContext)

        }else {

            let timeStamp = context!["TIMESTAMP"] as! String

            let applicationTimeStamp = applicationContext["TIMESTAMP"] as! String

            if timeStamp < applicationTimeStamp {

                self.setContext(applicationContext)

            }else if timeStamp > applicationTimeStamp {

                try? session.updateApplicationContext(context!)

            }

        }

    }

    func setContext(_ context:[String: Any]){ // (K)

        self.context = context

        let ABC = context["ABC"] as! String

        field.setText(ABC)

    }

    var TimeStamp: String { // (L)

        let date = Date()

        let formatter = DateFormatter()

        formatter.dateFormat = "yyyyMMddHHmmss"

        return formatter.string(from: date)

    }

}

(A) iPhoneとの通信のためのフレームワーク

(B) WCSessionクラスのデリゲートを設定。

(C) 選択肢配列。

(D) データを保持するためのプロパティ。

(E) 選択肢を表示するTableの初期化。UIKitとは様相が異なるので注意。

(F) iPhoneとの通信がサポートされているかどうか確認し、サポートされていれば、デリゲートをセットし、通信をアクティブにする。

(G) Tableを選択した時に呼ばれる。デリゲートメソッドではない。新しいcontextを作成し、setContextする。

(H) アクティブになったとき、contextが空なら、iPhoneに問い合わせ。通信がすぐにできないときは、通信ができたときに値を取得する仕様。

(I) WCSessionDelegateの必須メソッド。

(J)iPhoneからのupdateApplicationContext( )を受け取るメソッド。送られてきたcontextのTimestampを調べ、iPhone側の方が新しければ更新、Watch側が新しければ送り返すように設定。

(K) contextを元に、プロパティとラベルに値を流し込むメソッド。

(L) 現在時刻からタイムスタンプ文字列を返す。

 

以上で、Runすると次のような画面が出て、相互のアクションを送り合います。

※諸事情により、まだ実機では試していません。

f:id:JoeCola:20180809103422p:plain

 

iOSにAdMobを実装する

もちろん、公式頁 を見るのが早い。でも、英語…。

【旧式の手順】iOS6以前でも利用可能。

 

1. ADMOBアカウントを取得

もちろん、公式頁を見るのが早い。

2. ADMOB SDKをダウンロード

とりあえず、公式頁を見るのが早い。

もちろん、iOS

Package の googlemobileadssdkios.zip からダウンロードする。

3. XcodeでADMOBを実装

① Single View App を新規作成。名前はTEST_ADMOB程度で。

② 2.でダウンロードしたGoogleMobileAdsSdkiOS-x.x.xを確認し、

 File>Add Files To... でプロジェクトに追加する。copy items if needed にチェック。

 f:id:JoeCola:20180731001853p:plain

③以下のフレームワークを追加する。

 f:id:JoeCola:20180731002845p:plain

④コードを追加する。

************ 2018/9/29 追記 ************

AppDlegateにコードを追加する。

import GoogleMobileAds

と、

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        GADMobileAds.configure(withApplicationID: "ca-app-pub-xxxxxxxxxxxxxxxx~xxxxxxxxxx")

        return true

    }

 *********************************************

 ViewControllerにコードを追加する。

  • GoogleMobileAds をインポート
  • GADBannerViewDelegateを設定
  • GADBannerViewをviewに貼り付けてGADRequestを設定 

import UIKit

import GoogleMobileAds

 

class ViewController: UIViewController, GADBannerViewDelegate {

    

    var adHeight: CGFloat = 44.0

    

    override func viewDidLoad() {

        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.

        // AdMob 表示

        let bannerView = GADBannerView(adSize:kGADAdSizeBanner)

        bannerView.frame.origin = CGPoint(x: 0.0, y: self.view.frame.size.height - bannerView.frame.height)

        bannerView.frame.size = CGSize(width: self.view.frame.width, height: bannerView.frame.height)

        // AdMobで発行された広告ユニットIDを設定

        bannerView.adUnitID = "ca-app-pub-xxxxxxxxxxxxxxx/xxxxxxxxxx"

        bannerView.delegate = self

        bannerView.rootViewController = self

        let gadRequest:GADRequest = GADRequest()

        // テスト用の広告を表示する時のみ使用(申請時に削除)、

     // テスト機のIDを入力、もしくはkGADSimulatorID

        gadRequest.testDevices = ["x...x"]//[kGADSimulatorID]//

        bannerView.load(gadRequest)

        self.view.addSubview(bannerView)

        

        adHeight = bannerView.frame.height

    }

 

    override func didReceiveMemoryWarning() {

        super.didReceiveMemoryWarning()

        // Dispose of any resources that can be recreated.

    }

 

}

 ⑤Run

f:id:JoeCola:20180731005002p:plain

 出ました。ADMOB。これはシミュレータなので、

gadRequest.testDevices = [kGADSimulatorID]

 としている。