エラーコード 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種類のみにした。
- AppleWatch Simulator(以下AppleWatch)をiPhone Simulator(以下iPhone)をペアリング
- iPhoneとAppleWatchの値渡しの概要
- iPhone側のStoryBoardとViewControllerの修正
- AppleWatch側のInterfaceとInterfaceControllerの修正
まずは、AppleWatchアプリ作成のプロジェクトを新規作成する。
1. AppleWatch Simulator(以下Watch)をiPhone Simulator(以下iPhone)をペアリング
XCodeの Window > Devices and Simulators で右ペインの PAIRED WATCHES で+ボタンを押してペアリングさせる。
2. iPhoneとAppleWatchの値渡しの概要
値を送りたいタイミングで、次図の上にあるメソッドを使用する。
すぐに値を送るものと、機を見ていいタイミングで値を送るものとがある。
参照頁。このページは大変良い。
それで、次図の下にあるメソッドを構えておくと、値を受け取ることができる。
逆の送受信も同じ。
3. iPhone側のMainStoryBoardとViewControllerの修正
①MainStoryBoardの修正
次のように設定。
・TextField配置。ViewControllerにIBOutletを引っ張る。
・ButtonのA〜Cを配置。すべてのButtonをVirewControllerの同じActionに引っ張る。
② 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)
}
}
(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にセット。
② 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)
}
}
(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すると次のような画面が出て、相互のアクションを送り合います。
※諸事情により、まだ実機では試していません。
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 にチェック。
③以下のフレームワークを追加する。
④コードを追加する。
************ 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
出ました。ADMOB。これはシミュレータなので、
gadRequest.testDevices = [kGADSimulatorID]
としている。
HatenaBlog スタート
自分のためのメモブログスタート。