SlideShare uma empresa Scribd logo
1 de 76
keep a diary
2017.10.15 Bitz Co., Ltd. 村上幸雄
https://github.com/murakami/KeepADiary
https://www.slideshare.net/yukiomurakami
注意!CloudKitを有効にすると、そのバンドルIDでコンテナが
生成されます。
一度生成したコンテナは削除できないので、コードを動かす前
に、適切なバンドルIDに変更してください。
• 埼玉県朝霞市でソフトハウスを起業。
• macOSやiOS、Androidのアプリケーション開発を主に請け負う。
• 自社アプリの製作。
• Twitter: @m_yukio
• Facebook: yukio.murakami
• GitHub: murakami
• http://www.bitz.co.jp/weblog/
• Cocoa勉強会 BUKURO.swift
毎月上旬の月曜日に池袋FORESTで開会。
https://cocoa-study.connpass.com/
• MOSA BUKURO.swift
毎月中旬の金曜日に池袋FORESTで開催。
https://mosa.connpass.com/
• Cocoa勉強会 Swift Lab.
毎月下旬の土曜日に関東で開催。
https://cocoa-kanto.connpass.com/
振り返り: 2016/11
• 2016/11/14 Cocoa Study at Ikebukuro #5
• Bluemix再挑戦
• Project Linkingを試す
• 2016/11/19 Cocoa Study at Matudo #2
• ProxyResponder nibファイルを超えたTarget/action
• シェアウェア、やって良い事、悪い事
• 2016/11/25 第5回 MOSA自習室
• Swiftyを試す
• SwiftからGUI AppをコントロールするScriptarian
• PlantUMLのsalt言語
振り返り: 2016/12
• 2016/12/5 Cocoa Study at Ikebukuro #6
• Swiftyを試す
• GNUstep用アプリ”MyWiki.app”をビルドする
• MacOSX用アプリ”CardBook.app”をビルドする
• TouchBarDemoApp
• MindTo01sサイトの紹介
振り返り: 2017/1
• 2017/1/16 Cocoa Study at Ikebukuro #7
• Swiftyを試す
• iOS上のQRコードリーダーアプリのメールフィールド認識についての調査
• ジェスチャーやカスタムトランジションを利用して入力時やコンテンツ表示時に一工夫を加え
たUIの実装ポイントまとめ
• 2017/1/28 Cocoa Study at Matudo #3
• 単車の手帳
• macOS Cocoaを始める
• アイデア出しソフトウエア
• NSAtomicStoreサブクラスの作成
振り返り: 2017/2
• 2017/2/6 Cocoa Study at Ikebukuro #8
• ピラミッド原則のアプリ化について
• 2017/2/18 Cocoa Study at Matudo #4
• 2017/2/24 第6回 MOSA自習室
• スパゲッティ紀行
• 私の開発環境
• 効果的な学習方法について
• Hide scroll bar from PDFView
振り返り: 2017/3
• 2017/3/6 Cocoa Study at Ikebukuro #9
• Document-Based Application
• storyboardの中のNSWindow initialFirstResponderが効かない問題について
• Cocoaにおける状態の保存と復元その2
• 2017/3/17 MOSA BUKURO.swift #7
• 私の営業の失敗談
• 継承vs使用
• [macOS]Document-based Application
• pragma markによるメソッドの分類
• 2017/3/25 Cocoa Study at Matudo #5
• 最小限アウトラインプロセッサの作り方
振り返り: 2017/4
• 2017/4/3 Cocoa Study at Ikebukuro #10
• swiftによるアプリケーション開発-入門編
• [macOS]ヘルパ・オブジェクト
• Apple File System の Frequently Asked Questions
• Cocoaからのライブ変換(LiveConversion)の変更と監視
• 2017/4/14 MOSA BUKURO.swift #8
• swiftによるアプリケーション開発-入門編
• uncrustifyもういっぱい
• Doxygenでpdf出力
振り返り: 2017/5
• 2017/5/8 Cocoa Study at Ikebukuro #11
• swiftによるアプリケーション開発-入門編
• Playing with RESTful APIs
• 2017/5/15 AKIBA.swift x BUKURO.swift #9 合同勉強会
• キーチェーン・アクセスのバックアップと同期
• Macアプリのインストーラ作成ツールSwift版
• cocoaで作る最小限カード型データベースの実装
• iOS macOS クロスプラットフォーム
• CGAffineTransform 実践入門
• 2017/5/27 Cocoa Study at Matudo #6
• 最小限データーベースその2
• 最小限データーベースその3
振り返り: 2017/6
• 2017/6/5 Cocoa勉強会 BUKURO.swift #12
• 2017/6/16 MOSA BUKURO.swift #10 【WWDC2017特集!】
• WWDC2017
• WWDC2017レポート
• Wikipediaを用いた検索キーワードのカテゴリ連想拡張
• 渦巻き
• 2017/6/24 関東swift勉強会2017-6
• swiftによるアプリケーション開発-入門編
• 計算機プログラムの構造と解釈(SICP)
• swiftでハノイの塔
• swiftで三段論法
• swiftで最小限タートルグラフィック
振り返り: 2017/7
• 2017/7/3 Cocoa勉強会 BUKURO.swift #13
• Machスレッドスタック
• Swiftで8人の女王
• swiftでアナグラム
• 2017/7/14 MOSA BUKURO.swift #11
• swiftによるアプリケーション開発-入門編
• 人工知能を実装する 意味ネットワーク
• 2017/7/22 関東swift勉強会2017-7
• swiftで迷路の脱出
• swiftで迷路の作成
• PlaygroundでStoryboardの試作
• 空配列
振り返り: 2017/9
• 2017/9/4 Cocoa勉強会 BUKURO.swift #14
• Swiftによるアプリケーション開発:入門編
• AS.Parallel
• Cross-platformプロジェクト
• Objective-Cで書いていたライブラリをSwiftへ移植中に得られた知見など色々
• [macOS]Day One Classic 書類
• 2017/9/13 MOSA BUKURO.swift #12
• Swiftによるアプリケーション開発:入門編
• Designing for iPhone X
• Building Apps for iPhone X
• safeAreaLayoutGuide
• 2017/9/23 関東swift勉強会2017-9
• Control Enablerのswiftへの移植
• NSMenuItemのショートカットキー表記方式の変更
• Apple Technology Lab. (よろず相談室)
振り返り: 2017/10
• 2017/10/02 Cocoa勉強会 BUKURO.swift #15
• 日記をお空に預ける!
• 消費者スペクトラムとプロパガンダ
• macOSのFinder
• Unicodeの正規化
How does Apple File System handle filenames?
APFS accepts only valid UTF-8 encoded filenames for creation, and preserves both case and normalization of the filename on disk in all variants. APFS,
like HFS+, is case-sensitive on iOS and is available in case-sensitive and case-insensitive variants on macOS, with case-insensitive being the default.
In macOS High Sierra, APFS is normalization-insensitive in both the case-insensitive and case-sensitive variants, using a hash-based native normalization
scheme. In iOS 11, APFS is normalization-insensitive as well, using either a native normalization scheme (erase restores only) or runtime normalization
scheme (upgrades from previous versions). Runtime normalization will also be available in iOS 10.3.3 and macOS Sierra 10.12.6. Being normalization-
insensitive ensures that normalization variants of a filename cannot be created in the same directory, and that a filename can be found with any of its
normalization variants. This means that developers don’t need to do any additional work to ensure correct normalization behavior in these versions of
macOS and iOS.
Some differences between how APFS and HFS+ handle filenames include the following:
• APFS implements normalization and case insensitivity according to the Unicode 9.0 standard; this enables APFS to support a wider range
of languages for these features than HFS+, which is based on Unicode 3.2.
• APFS preserves the normalization of the filename and uses hashes of the normalized form of the filename to provide normalization
insensitivity, whereas HFS+ stores the normalized form of the filename on disk to provide normalization insensitivity.
• Calling readdir(2) on a directory in APFS returns filenames in hash order, whereas HFS+ returns filenames in lexicographical order.
• While both filesystems expect filenames to be encoded in UTF-8, APFS stores filenames on disk in UTF-8 encoding, whereas HFS stores
filenames on disk in UTF-16 encoding.
• APFS doesn’t allow files to be created with filenames that contain unassigned codepoints in the Unicode 9.0 standard, whereas HFS+ does.
In iOS 10.3 and in the case-sensitive variant of the developer preview of APFS in macOS Sierra, APFS is normalization-sensitive. For these versions,
developers should be aware of behavior differences between normalization sensitivity and insensitivity that may arise when a device upgrades macOS or
iOS and migrates the filesystem from HFS+ to APFS. For example, attempting to create a file using one normalization behavior and then opening that file
using another normalization behavior may result in ENOENT, or “File Not Found” errors. Additionally, storing filenames externally, such as in the defaults
database, Core Data, or iCloud storage may cause problems if the normalization scheme of the filename being stored is different from what exists on disk.
To avoid introducing bugs in your code with mismatched Unicode normalization (for iOS 10.3.0, 10.3.1 and 10.3.2) in filenames, do the following:
• Use high-level Foundation APIs such as NSFileManager and NSURL when interacting with the filesystem.
• Use the fileSystemRepresentation property of NSURL objects when creating and opening files with lower-level filesystem APIs such
as POSIX open(2), or when storing filenames externally from the filesystem.
予定
• 2017/10/20 Swift勉強会 Developer’s Tech. Lab.
• アウトライプロセッサ進捗会議
• お空に日記を預ける
• Developer's Tech Lab
• iOSDC 2017の参加報告
• Cocoa勉強会 MOSA 合同勉強会
BUKURO.swift 2017-11
keep a diary
• 発端
• どう実装するか?
• Day Oneのデータ形式を探る
• CloudKit
• Cross-platform プロジェクト
• macOSアプリケーション
• keep a diary
発端
• Day Oneという日記アプリを利用しています。
• 種類が増えて、今後が不安。
• Day One Classic
売り切り。発表者は、これのユーザ。新規購入不可。
• Day One 2.0
別アプリとしてリリース。新規購入不可。
• Day One Premium/Plus/Basic
定額制プラン。
Sync
• Day One
独自サーバ。
• iCloud
探せばデータを見つけることは出来るが、見えないと考
えるべきか。
~/Library/Mobile Documents/*~com~dayoneapp~dayone/Documents/Journal_dayone
• Dropbox
データは見える場所。
~/Dropbox/アプリ/Day One/Journal.dayone
Day One
+- Journal.dayone
+- entries
| 1A573C0B559248A991C4AC0F0EA8B1E7.doentry
| 1FB23D983DF341079532B0F9381BCFCC.doentry
| :
+- photos
1A573C0B559248A991C4AC0F0EA8B1E7.jpg
2D37E853754E43EFBA0FFBAEEB6A8B40.jpg
:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Activity</key>
<string>Automotive</string>
<key>Creation Date</key>
<date>2016-07-18T08:51:34Z</date>
<key>Creator</key>
<dict>
<key>Device Agent</key>
<string>iPhone/iPhone7,2</string>
<key>Generation Date</key>
<date>2016-07-18T08:51:34Z</date>
<key>Host Name</key>
<string>iPhone6GB128Gold</string>
<key>OS Agent</key>
<string>iOS/9.3.2</string>
<key>Software Agent</key>
<string>Day One iOS/1.17.9</string>
</dict>
<key>Entry Text</key>
<string>AT教習のゼッケン
下で待つ。準備なし</string>
:
どう実装するか?
お空に預ける!
データは?
• ローカルに保持
• 独自サーバ
• クラウドサービス
• iCloud
• Firebase
Day Oneの
データ形式を探る
entriesとphotos
• entriesディレクトリ
一つの日記の本体の情報。
• 1A573C0B559248A991C4AC0F0EA8B1E7.doentry
ユニークIDがファイル名。
photosとの対応は、ユニークIDで。
• photosディレクトリ
写真。
• 1A573C0B559248A991C4AC0F0EA8B1E7.jpg
ユニークIDがファイル名。
entriesとの対応は、ユニークIDで。
.doentryファイルを
ダンプ
@IBAction func performDumpDayOneClassic(sender: AnyObject) {
let openPanel = NSOpenPanel()
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.canChooseFiles = false
openPanel.begin { (result) -> Void in
if result == NSFileHandlingPanelOKButton {
let inst = KeepADiary()
inst.dump(url: openPanel.urls[0])
}
}
}
class KeepADiary {
public func dump(url: URL) -> Void {
print(url)
var entriesUrl = url
entriesUrl.appendPathComponent("entries", isDirectory: true)
print(entriesUrl)
var fileList: [String] {
do {
return try FileManager.default.contentsOfDirectory(atPath: entriesUrl.path)
} catch {
return []
}
}
for fileName in fileList {
var fileUrl = entriesUrl
fileUrl.appendPathComponent(fileName)
let plistXML: NSData = FileManager.default.contents(atPath: fileUrl.path)!
as NSData
let temp = try! PropertyListSerialization.propertyList(from:plistXML as Data,
options: [], format: nil) as! [String:Any]
print(temp)
}
}
}
["Entry Text": AT教習のゼッケン
下で待つ。準備なし, "UUID": 38906447EE98439F9854DA354D9E95FA, "Step Count": 6909, "Creator": {
"Device Agent" = "iPhone/iPhone7,2";
"Generation Date" = "2016-07-18 08:51:34 +0000";
"Host Name" = iPhone6GB128Gold;
"OS Agent" = "iOS/9.3.2";
"Software Agent" = "Day One iOS/1.17.9";
}, "Ignore Step Count": 1, "Location": {
"Administrative Area" = "U57fcU7389U770c";
Country = "U65e5U672c";
Latitude = "35.79399523328598";
Locality = "U548cU5149U5e02";
Longitude = "139.637718340512";
"Place Name" = "U4e0bU65b0U50095U4e01U76ee27U756a1U53f7";
Region = {
Center = {
Latitude = "35.79412019872211";
Longitude = "139.6382801";
};
Radius = "70.86956812177714";
};
}, "Starred": 0, "Time Zone": Asia/Tokyo, "Activity": Automotive, "Weather": {
Celsius = 32;
Description = "Partly Cloudy";
Fahrenheit = 90;
IconName = "fair.png";
"Pressure MB" = 1005;
"Relative Humidity" = 55;
Service = HAMweather;
"Sunrise Date" = "2016-07-17 19:39:47 +0000";
"Sunset Date" = "2016-07-18 09:56:36 +0000";
"Wind Bearing" = 180;
"Wind Chill Celsius" = 32;
"Wind Speed KPH" = 15;
}, "Creation Date": 2016-07-18 08:51:34 +0000]
CloudKit
https://developer.apple.com/jp/documentation/DataManagement/Conceptual/CloudKitQui
iCloud
• キー値ストレージ
UserDefaultsと同様。1アプリ1MB。
• ドキュメントストレージ
文書ファイル。iCloudアカウントの容量から。
• CoreDataストレージ
←廃止
• CloudKit
他ユーザと共有できるデータ
どれくらい使える?
• 10 GB Asset storage
• 100 MB Database storage
• 2 GB Data transfer
• 40 Requests per second
• Overage Fees
• Asset storage $0.03/GB
• Database storage $3.00/GB
• Data transfer $0.10/GB
• Requests per sec $100 per 10 requests
CloudKitを利用
iCloud Containers
Development Data
フィールド型 クラス Description
Asset CKAsset
大容量ファイルをレコードに
関連づけて別個に保存
Bytes NSData
バイトバッファをレコードに
保存するためのラッパー
Date/Time NSDate 時間軸上のある一時点
Double NSNumber 倍精度浮動小数点数
Int(64) NSNumber 整数
Location CLLocation
地理的座標(経緯度)および
高度
Reference CKReference
あるオブジェクトから別のオ
ブジェクトを参照するリレー
ション
String NSString
書き換え不可のテキスト文字
列
List NSArray
上記いずれかのフィールド型
の配列
コンテナ
アプリケーション
Cross-platform
プロジェクト
参考として試して作ってみる
。
空プロジェクトを生成。
macOSターゲットを追加。
オススメのターゲット名。
macOSとiOS/watchOS/tvOSは同じバンドル
IDをつけられない
macOSとiOS/watchOS/tvOSは同じentitlements
ファイル名をつけない方がいい
macOS
アプリケーション
最近の開発環境について
Storyboardを使用
• Storyboard使用を選択すると、生成されるプロジェクトの
構成が最新のものになる。
• 例えば、Document-based Applicationの場合、以前は、
DocumentクラスにOutletとActionが。
Storyboard使用を選択すると、Cocoa touchの
UIViewControllerに似たNSViewControllerが利用される。
initialFirstResponder
• NSWindowのアウトレットinitialFirstResponderをViewに
繋げると、それが最初にアクティブになる。
• Storyboardだと、Window Controller SceneとView
Controller Sceneは別Sceneのため、繋げられない。
コードで設定
class ViewController: NSViewController {
override func awakeFromNib() {
self.view.window?.initialFirstResponder = self.nameField
}
}
この問題、BUKURO.swiftで議論した際に、汎用的な方法の提案もありましたが、
複雑だったので、この方法がベターかなと思っています。
http://www.mindto01s.com/2017/02/26/e97ab989_f357_44c7_8810_21ce715d9d2b.html
いい方法をご存知な方がいらっしゃいましたら、是非。
keep a diary
位置情報
写真
コンテナ
コンテナID
デフォルトコンテナ
let container: CKContainer = CKContainer.default()
カスタムコンテナ
let container: CKContainer = CKContainer(identifier:
“iCloud.com.example.KeepADiary”)
データベース
公開データベース
let publicDatabase: CKDatabase = container.publicCloudDatabase
非公開データベース
let privateDatabase: CKDatabase = container.privateCloudDatabase
共有データベース
let sharedDatabase: CKDatabase = container.sharedCloudDatabase
コンテナ
アカウントの確認
CKContainer.default().accountStatus(completionHandler: {
accountStatus, error in
if error != nil {
print(error ?? "")
}
else if accountStatus == .noAccount {
let alert = UIAlertController.init(title: "Sign in to iCloud",
message: "Sign in to your iCloud account to write records.On the Home screen,
launch Settings, tap iCloud, and enter your Apple ID.Turn iCloud Drive on.If you don't have an iCloud
account, tap Create a new Apple ID.",
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Okay", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
else {
print(“Insert your just-in-time schema code here”)
}
})
CloudKit Dashboard
開発者アカウント
試験用アカウント
メタデータ・インデックスを有効にする
• 開発環境
• スキーマ生成
• レコード追加
• スキーマを配備
• 開発環境から実稼働環境へスキーマをコピー
• 実稼働環境
• レコード追加
追加
/* レコードIDを生成 */
let uuidString = UUID.init().uuidString
let diaryRecordID: CKRecordID = CKRecordID(recordName: uuidString)
/* レコード・オブジェクトを生成 */
let diaryRecord: CKRecord = CKRecord(recordType: "Diary", recordID: diaryRecordID)
/* レコードのフィールドを設定 */
diaryRecord["UUID"] = uuidString as CKRecordValue
diaryRecord.setObject("本文です。" as NSString, forKey: "EntryText")
diaryRecord.setObject(Date() as NSDate, forKey: "DiaryDate")
/* レコードを保存する */
database.save(diaryRecord, completionHandler: { (record, error) in
if error != nil {
print(error)
}
else {
print(record)
}
})
更新
/* レコードIDからレコードを取得する */
database.fetch(withRecordID: diaryRecordID, completionHandler: { (record, error) in
if error != nil {
print(error)
}
else {
/* 値を更新する */
record.setObject("本文を更新します。" as NSString, forKey: "EntryText")
/* レコードを保存する */
database.save(diaryRecord, completionHandler: { (record, error) in
if error != nil {
print(error)
}
else {
print(record)
}
})
}
})
削除
/* 追加で使ったレコードIDで取得 */
database.delete(withRecordID: diaryRecordID, completionHandler: { (recordId, error) in
if error != nil {
print(error)
}
else {
print(recordId)
}
})
検索
/* 検索条件 */
let now = Date()
var calendar = Calendar.current
calendar.locale = Locale(identifier: "ja")
var component = calendar.dateComponents([.year, .month, .day], from: now)
component.hour = 0
component.minute = 0
component.second = 0
let beginDate: NSDate = calendar.date(from:component)! as NSDate
let endDate: NSDate = NSDate(timeInterval: (60.0 * 60.0 * 24.0), since: beginDate as Date)
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "ja_JP")
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
let beginDateString = dateFormatter.string(from: beginDate as Date)
let endDateString = dateFormatter.string(from: endDate as Date)
let predicate = NSPredicate(format: "DiaryDate != %@", NSDate())
let query : CKQuery = CKQuery(recordType: "Diary", predicate: predicate)
/* 検索する */
database.perform(query, inZoneWith: nil, completionHandler: {
results, error in
if error != nil {
print(error)
}
else {
print(results)
/* 検索した結果 */
for record in results! {
/* 削除する */
database.delete(withRecordID: record.recordID, completionHandler: { (recordId, error) in
if error != nil {
print(error)
}
else {
print(recordId)
}
})
}
}
})
条件
更新を通知
Diary
Diary
1: 保存
3: 通知
2: 更新
サブスクリプション保存
/* 条件 */
let predicate = NSPredicate(format: "TRUEPREDICATE")
let subscription = CKSubscription(recordType: "Diary",
predicate: predicate,
options: [CKSubscriptionOptions.firesOnRecordCreation,
CKSubscriptionOptions.firesOnRecordUpdate])
/* 通知 */
subscription.notificationInfo = CKNotificationInfo()
subscription.notificationInfo?.alertBody = "Item has been added."
subscription.notificationInfo?.alertLocalizationKey = "Diary record has changed!"
subscription.notificationInfo?.shouldBadge = true
/* 保存 */
database.save(subscription, completionHandler: {
subscription, error in
if error != nil {
print(error)
}
else {
print(subscription)
}
})
macOS
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApplication.shared().registerForRemoteNotifications(matching:
[.alert, .sound])
}
func application(_ application: NSApplication,
didRegisterForRemoteNotificationsWithDeviceToken
deviceToken: Data) {
}
func application(_ application: NSApplication,
didFailToRegisterForRemoteNotificationsWithError
error: Error) {
}
func application(_ application: NSApplication,
didReceiveRemoteNotification userInfo:
[String : Any]) {
}
iOS
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.delegate = self
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
if error != nil {
return
}
if granted {
DispatchQueue.main.async {
application.registerForRemoteNotifications()
}
} else {
}
}
}
else {
let settings = UIUserNotificationSettings(types: UIUserNotificationType.alert, categories:nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
return true
}
func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) {
}
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
}
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
let cloudKitNotification: CKNotification = CKNotification(fromRemoteNotificationDictionary: userInfo)
let alertBody = cloudKitNotification.alertBody
print("alertBody: (String(describing: alertBody))")
if cloudKitNotification.notificationType == .query {
let queryNotification = cloudKitNotification as! CKQueryNotification
let recordID = queryNotification.recordID
}
}
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
}
/* 前面で通知受信 */
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler:
@escaping (UNNotificationPresentationOptions) -> Void) {
}
/* 通知を選択 */
@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
completionHandler()
}
動作確認
• 実機で行う。
• CloudKitのダッシュボードでレコードを追加する。
DEMO
ありがとうございます

Mais conteúdo relacionado

Mais procurados

執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話
執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話
執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話Takayuki Shimizukawa
 
Linux環境でLibreOfficeをビルドしてみようかね
Linux環境でLibreOfficeをビルドしてみようかねLinux環境でLibreOfficeをビルドしてみようかね
Linux環境でLibreOfficeをビルドしてみようかねNaruhiko Ogasawara
 
SIerでもSphinxを使いたい!総括
SIerでもSphinxを使いたい!総括SIerでもSphinxを使いたい!総括
SIerでもSphinxを使いたい!総括kk_Ataka
 
ドキュメントを作りたくなってしまう魔法のツール「Sphinx」
ドキュメントを作りたくなってしまう魔法のツール「Sphinx」ドキュメントを作りたくなってしまう魔法のツール「Sphinx」
ドキュメントを作りたくなってしまう魔法のツール「Sphinx」Yoshiki Shibukawa
 
Sphinxで作る貢献しやすい ドキュメント翻訳の仕組み
Sphinxで作る貢献しやすいドキュメント翻訳の仕組みSphinxで作る貢献しやすいドキュメント翻訳の仕組み
Sphinxで作る貢献しやすい ドキュメント翻訳の仕組みTakayuki Shimizukawa
 
LibreOffice を Windows 上でビルドする UPDATE2
LibreOffice を Windows 上でビルドする UPDATE2LibreOffice を Windows 上でビルドする UPDATE2
LibreOffice を Windows 上でビルドする UPDATE2Tomofumi Yagi
 
LibreOfficeをWindows上でビルドする
LibreOfficeをWindows上でビルドするLibreOfficeをWindows上でビルドする
LibreOfficeをWindows上でビルドするTomofumi Yagi
 
ドキュメントシステムはこれを使え2015年版
ドキュメントシステムはこれを使え2015年版ドキュメントシステムはこれを使え2015年版
ドキュメントシステムはこれを使え2015年版Keiichiro Shikano
 
マークアップ言語の拡張 メリットとデメリット #hankumi
マークアップ言語の拡張 メリットとデメリット #hankumiマークアップ言語の拡張 メリットとデメリット #hankumi
マークアップ言語の拡張 メリットとデメリット #hankumiTakeshi Komiya
 
Sphinx で手軽に作るドキュメント
Sphinx で手軽に作るドキュメントSphinx で手軽に作るドキュメント
Sphinx で手軽に作るドキュメントGo Yamada
 
ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会
ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会
ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会Takayuki Shimizukawa
 
SIerでもSphinxを使いたい! 前編
SIerでもSphinxを使いたい! 前編SIerでもSphinxを使いたい! 前編
SIerでもSphinxを使いたい! 前編kk_Ataka
 
Rancherでwindows server上のコンテナを管理できるか
Rancherでwindows server上のコンテナを管理できるかRancherでwindows server上のコンテナを管理できるか
Rancherでwindows server上のコンテナを管理できるかTakashi Kanai
 
GitLabを骨までしゃぶりつくす@ゆるUniStudy#7
GitLabを骨までしゃぶりつくす@ゆるUniStudy#7GitLabを骨までしゃぶりつくす@ゆるUniStudy#7
GitLabを骨までしゃぶりつくす@ゆるUniStudy#7Wataru NOGUCHI
 
Idcfクラウドで始める構築自動化
Idcfクラウドで始める構築自動化Idcfクラウドで始める構築自動化
Idcfクラウドで始める構築自動化智之 大野
 
コンテナで作る開発環境 (20161104 CodeIgniter Night)
コンテナで作る開発環境 (20161104 CodeIgniter Night)コンテナで作る開発環境 (20161104 CodeIgniter Night)
コンテナで作る開発環境 (20161104 CodeIgniter Night)智之 大野
 
WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~
WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~
WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~Toru Miki
 

Mais procurados (20)

執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話
執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話
執筆中のPythonプロフェッショナルプログラミング第2版でsphinxを使っている話
 
Linux環境でLibreOfficeをビルドしてみようかね
Linux環境でLibreOfficeをビルドしてみようかねLinux環境でLibreOfficeをビルドしてみようかね
Linux環境でLibreOfficeをビルドしてみようかね
 
SIerでもSphinxを使いたい!総括
SIerでもSphinxを使いたい!総括SIerでもSphinxを使いたい!総括
SIerでもSphinxを使いたい!総括
 
ドキュメントを作りたくなってしまう魔法のツール「Sphinx」
ドキュメントを作りたくなってしまう魔法のツール「Sphinx」ドキュメントを作りたくなってしまう魔法のツール「Sphinx」
ドキュメントを作りたくなってしまう魔法のツール「Sphinx」
 
Sphinxで作る貢献しやすい ドキュメント翻訳の仕組み
Sphinxで作る貢献しやすいドキュメント翻訳の仕組みSphinxで作る貢献しやすいドキュメント翻訳の仕組み
Sphinxで作る貢献しやすい ドキュメント翻訳の仕組み
 
LibreOffice を Windows 上でビルドする UPDATE2
LibreOffice を Windows 上でビルドする UPDATE2LibreOffice を Windows 上でビルドする UPDATE2
LibreOffice を Windows 上でビルドする UPDATE2
 
LibreOfficeをWindows上でビルドする
LibreOfficeをWindows上でビルドするLibreOfficeをWindows上でビルドする
LibreOfficeをWindows上でビルドする
 
真Drone入門
真Drone入門真Drone入門
真Drone入門
 
ドキュメントシステムはこれを使え2015年版
ドキュメントシステムはこれを使え2015年版ドキュメントシステムはこれを使え2015年版
ドキュメントシステムはこれを使え2015年版
 
マークアップ言語の拡張 メリットとデメリット #hankumi
マークアップ言語の拡張 メリットとデメリット #hankumiマークアップ言語の拡張 メリットとデメリット #hankumi
マークアップ言語の拡張 メリットとデメリット #hankumi
 
Sphinx で手軽に作るドキュメント
Sphinx で手軽に作るドキュメントSphinx で手軽に作るドキュメント
Sphinx で手軽に作るドキュメント
 
Docker meetup tokyo_public_r001
Docker meetup tokyo_public_r001Docker meetup tokyo_public_r001
Docker meetup tokyo_public_r001
 
ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会
ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会
ドキュメンテーションを加速するストレスフリーの作図ツール『blockdiag』 jus2011年6月勉強会
 
SIerでもSphinxを使いたい! 前編
SIerでもSphinxを使いたい! 前編SIerでもSphinxを使いたい! 前編
SIerでもSphinxを使いたい! 前編
 
Rancherでwindows server上のコンテナを管理できるか
Rancherでwindows server上のコンテナを管理できるかRancherでwindows server上のコンテナを管理できるか
Rancherでwindows server上のコンテナを管理できるか
 
GitLabを骨までしゃぶりつくす@ゆるUniStudy#7
GitLabを骨までしゃぶりつくす@ゆるUniStudy#7GitLabを骨までしゃぶりつくす@ゆるUniStudy#7
GitLabを骨までしゃぶりつくす@ゆるUniStudy#7
 
Idcfクラウドで始める構築自動化
Idcfクラウドで始める構築自動化Idcfクラウドで始める構築自動化
Idcfクラウドで始める構築自動化
 
コンテナで作る開発環境 (20161104 CodeIgniter Night)
コンテナで作る開発環境 (20161104 CodeIgniter Night)コンテナで作る開発環境 (20161104 CodeIgniter Night)
コンテナで作る開発環境 (20161104 CodeIgniter Night)
 
LXDのすすめ
LXDのすすめLXDのすすめ
LXDのすすめ
 
WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~
WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~
WordPress サイト制作におけるデプロイメントを考える ~Git とデプロイメントサービスの活用~
 

Semelhante a Keep a diary

Chefで始めるWindows Server構築
Chefで始めるWindows Server構築Chefで始めるWindows Server構築
Chefで始めるWindows Server構築Takashi Kanai
 
ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜
ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜
ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜Hideki Takase
 
Docker 再入門 2016 update
Docker 再入門 2016 updateDocker 再入門 2016 update
Docker 再入門 2016 updateShiojiri Ohhara
 
ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来
ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来
ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来Kazuto Kusama
 
JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話
JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話
JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話Yahoo!デベロッパーネットワーク
 
Document based application
Document based applicationDocument based application
Document based application幸雄 村上
 
serverless openstack 101
serverless openstack 101serverless openstack 101
serverless openstack 101Naoto Gohko
 
アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)
アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)
アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)Takashi Sogabe
 
OSSとクラウドによるコンピューティングモデルの変化
OSSとクラウドによるコンピューティングモデルの変化OSSとクラウドによるコンピューティングモデルの変化
OSSとクラウドによるコンピューティングモデルの変化Nobuyori Takahashi
 
fluxflex meetup in Tokyo
fluxflex meetup in Tokyofluxflex meetup in Tokyo
fluxflex meetup in TokyoKyosuke Inoue
 
Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-
Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-
Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-natsumo
 
Windows Server 2016上でLinuxコンテナが動いた!
Windows Server 2016上でLinuxコンテナが動いた!Windows Server 2016上でLinuxコンテナが動いた!
Windows Server 2016上でLinuxコンテナが動いた!Takashi Kanai
 
localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話
localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話
localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話真治 米田
 
SphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまで
SphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまでSphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまで
SphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまでStudy Group by SciencePark Corp.
 
実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~
実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~
実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~Saki Homma
 
SwiftとCocoaPodsで始めるサクサクiOS開発!
SwiftとCocoaPodsで始めるサクサクiOS開発! SwiftとCocoaPodsで始めるサクサクiOS開発!
SwiftとCocoaPodsで始めるサクサクiOS開発! Koji Shiraishi
 
はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-
はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-
はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-Saki Homma
 

Semelhante a Keep a diary (20)

Swift の問題点
Swift の問題点Swift の問題点
Swift の問題点
 
Chefで始めるWindows Server構築
Chefで始めるWindows Server構築Chefで始めるWindows Server構築
Chefで始めるWindows Server構築
 
ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜
ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜
ロボットシステムのつくりかた 〜Robot Operating Systemというアプローチ〜
 
Docker 再入門 2016 update
Docker 再入門 2016 updateDocker 再入門 2016 update
Docker 再入門 2016 update
 
ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来
ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来
ひしめき合うOpen PaaSを徹底解剖! PaaSの今と未来
 
JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話
JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話
JSUG 2018/02/05 SpringOnePlatform2017参加報告 プラットフォーム関連のお話
 
Osoljp201210 oi swift
Osoljp201210 oi swiftOsoljp201210 oi swift
Osoljp201210 oi swift
 
RUDP
RUDPRUDP
RUDP
 
Document based application
Document based applicationDocument based application
Document based application
 
serverless openstack 101
serverless openstack 101serverless openstack 101
serverless openstack 101
 
アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)
アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)
アプリケーションエンジニアのためのクラウドインフラ再入門 (2/3)
 
OSSとクラウドによるコンピューティングモデルの変化
OSSとクラウドによるコンピューティングモデルの変化OSSとクラウドによるコンピューティングモデルの変化
OSSとクラウドによるコンピューティングモデルの変化
 
fluxflex meetup in Tokyo
fluxflex meetup in Tokyofluxflex meetup in Tokyo
fluxflex meetup in Tokyo
 
Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-
Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-
Swiftアプリにプッシュ通知を組み込もう!-【番外編】SDKのインポート方法-
 
Windows Server 2016上でLinuxコンテナが動いた!
Windows Server 2016上でLinuxコンテナが動いた!Windows Server 2016上でLinuxコンテナが動いた!
Windows Server 2016上でLinuxコンテナが動いた!
 
localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話
localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話
localstackによるAWS Lambdaの開発環境を、miniconda上でつくったら簡単便利だった話
 
SphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまで
SphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまでSphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまで
SphinxのCIの続き Azure DevOpsでのビルド結果を、認証付きAzure App Serviceに公開するところまで
 
実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~
実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~
実践 Web App for Containers! ~コンテナ開発の基礎からDevOps環境の構築まで~
 
SwiftとCocoaPodsで始めるサクサクiOS開発!
SwiftとCocoaPodsで始めるサクサクiOS開発! SwiftとCocoaPodsで始めるサクサクiOS開発!
SwiftとCocoaPodsで始めるサクサクiOS開発!
 
はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-
はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-
はじめてのAzure Web App for Containers! -コンテナの基礎から DevOps 環境の構築まで-
 

Mais de 幸雄 村上

アプリケーション識別子.pdf
アプリケーション識別子.pdfアプリケーション識別子.pdf
アプリケーション識別子.pdf幸雄 村上
 
圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf幸雄 村上
 
分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み幸雄 村上
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する幸雄 村上
 
え!それって参照渡し?
え!それって参照渡し?え!それって参照渡し?
え!それって参照渡し?幸雄 村上
 
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)幸雄 村上
 
AppleScriptとは何ぞや
AppleScriptとは何ぞやAppleScriptとは何ぞや
AppleScriptとは何ぞや幸雄 村上
 
Web API 通信の符号化について
Web API 通信の符号化についてWeb API 通信の符号化について
Web API 通信の符号化について幸雄 村上
 
Master-Detail App を実装する
Master-Detail App を実装するMaster-Detail App を実装する
Master-Detail App を実装する幸雄 村上
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する幸雄 村上
 
Getting a packet trace
Getting a packet traceGetting a packet trace
Getting a packet trace幸雄 村上
 
The Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKitThe Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKit幸雄 村上
 
Swiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装するSwiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装する幸雄 村上
 
ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦幸雄 村上
 
IBM Watson Services for Core ML
IBM Watson Services for Core MLIBM Watson Services for Core ML
IBM Watson Services for Core ML幸雄 村上
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス幸雄 村上
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス幸雄 村上
 

Mais de 幸雄 村上 (20)

アプリケーション識別子.pdf
アプリケーション識別子.pdfアプリケーション識別子.pdf
アプリケーション識別子.pdf
 
圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf圧縮ネイティブ・ライブラリについて.pdf
圧縮ネイティブ・ライブラリについて.pdf
 
分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み分散環境におけるジャストインタイム設定の試み
分散環境におけるジャストインタイム設定の試み
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する
 
え!それって参照渡し?
え!それって参照渡し?え!それって参照渡し?
え!それって参照渡し?
 
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
プライバシーとセキュリティ(リモート通知のデバイストークンの扱いなど)
 
AppleScriptなど
AppleScriptなどAppleScriptなど
AppleScriptなど
 
MojaveのDark Mode
MojaveのDark ModeMojaveのDark Mode
MojaveのDark Mode
 
AppleScriptとは何ぞや
AppleScriptとは何ぞやAppleScriptとは何ぞや
AppleScriptとは何ぞや
 
Web API 通信の符号化について
Web API 通信の符号化についてWeb API 通信の符号化について
Web API 通信の符号化について
 
Master-Detail App を実装する
Master-Detail App を実装するMaster-Detail App を実装する
Master-Detail App を実装する
 
SwiftのOptionalを理解する
SwiftのOptionalを理解するSwiftのOptionalを理解する
SwiftのOptionalを理解する
 
Getting a packet trace
Getting a packet traceGetting a packet trace
Getting a packet trace
 
The Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKitThe Bash in Tokyo : AppKitとUIKit
The Bash in Tokyo : AppKitとUIKit
 
Swiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装するSwiftでブロックチェーンを実装する
Swiftでブロックチェーンを実装する
 
ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦ゲームの企画書づくりに挑戦
ゲームの企画書づくりに挑戦
 
IBM Watson Services for Core ML
IBM Watson Services for Core MLIBM Watson Services for Core ML
IBM Watson Services for Core ML
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス
 
独自Documentクラス
独自Documentクラス独自Documentクラス
独自Documentクラス
 
Bukuro.makers
Bukuro.makersBukuro.makers
Bukuro.makers
 

Keep a diary

  • 1. keep a diary 2017.10.15 Bitz Co., Ltd. 村上幸雄
  • 3. • 埼玉県朝霞市でソフトハウスを起業。 • macOSやiOS、Androidのアプリケーション開発を主に請け負う。 • 自社アプリの製作。 • Twitter: @m_yukio • Facebook: yukio.murakami • GitHub: murakami • http://www.bitz.co.jp/weblog/
  • 4. • Cocoa勉強会 BUKURO.swift 毎月上旬の月曜日に池袋FORESTで開会。 https://cocoa-study.connpass.com/ • MOSA BUKURO.swift 毎月中旬の金曜日に池袋FORESTで開催。 https://mosa.connpass.com/ • Cocoa勉強会 Swift Lab. 毎月下旬の土曜日に関東で開催。 https://cocoa-kanto.connpass.com/
  • 5. 振り返り: 2016/11 • 2016/11/14 Cocoa Study at Ikebukuro #5 • Bluemix再挑戦 • Project Linkingを試す • 2016/11/19 Cocoa Study at Matudo #2 • ProxyResponder nibファイルを超えたTarget/action • シェアウェア、やって良い事、悪い事 • 2016/11/25 第5回 MOSA自習室 • Swiftyを試す • SwiftからGUI AppをコントロールするScriptarian • PlantUMLのsalt言語
  • 6. 振り返り: 2016/12 • 2016/12/5 Cocoa Study at Ikebukuro #6 • Swiftyを試す • GNUstep用アプリ”MyWiki.app”をビルドする • MacOSX用アプリ”CardBook.app”をビルドする • TouchBarDemoApp • MindTo01sサイトの紹介
  • 7. 振り返り: 2017/1 • 2017/1/16 Cocoa Study at Ikebukuro #7 • Swiftyを試す • iOS上のQRコードリーダーアプリのメールフィールド認識についての調査 • ジェスチャーやカスタムトランジションを利用して入力時やコンテンツ表示時に一工夫を加え たUIの実装ポイントまとめ • 2017/1/28 Cocoa Study at Matudo #3 • 単車の手帳 • macOS Cocoaを始める • アイデア出しソフトウエア • NSAtomicStoreサブクラスの作成
  • 8. 振り返り: 2017/2 • 2017/2/6 Cocoa Study at Ikebukuro #8 • ピラミッド原則のアプリ化について • 2017/2/18 Cocoa Study at Matudo #4 • 2017/2/24 第6回 MOSA自習室 • スパゲッティ紀行 • 私の開発環境 • 効果的な学習方法について • Hide scroll bar from PDFView
  • 9. 振り返り: 2017/3 • 2017/3/6 Cocoa Study at Ikebukuro #9 • Document-Based Application • storyboardの中のNSWindow initialFirstResponderが効かない問題について • Cocoaにおける状態の保存と復元その2 • 2017/3/17 MOSA BUKURO.swift #7 • 私の営業の失敗談 • 継承vs使用 • [macOS]Document-based Application • pragma markによるメソッドの分類 • 2017/3/25 Cocoa Study at Matudo #5 • 最小限アウトラインプロセッサの作り方
  • 10. 振り返り: 2017/4 • 2017/4/3 Cocoa Study at Ikebukuro #10 • swiftによるアプリケーション開発-入門編 • [macOS]ヘルパ・オブジェクト • Apple File System の Frequently Asked Questions • Cocoaからのライブ変換(LiveConversion)の変更と監視 • 2017/4/14 MOSA BUKURO.swift #8 • swiftによるアプリケーション開発-入門編 • uncrustifyもういっぱい • Doxygenでpdf出力
  • 11. 振り返り: 2017/5 • 2017/5/8 Cocoa Study at Ikebukuro #11 • swiftによるアプリケーション開発-入門編 • Playing with RESTful APIs • 2017/5/15 AKIBA.swift x BUKURO.swift #9 合同勉強会 • キーチェーン・アクセスのバックアップと同期 • Macアプリのインストーラ作成ツールSwift版 • cocoaで作る最小限カード型データベースの実装 • iOS macOS クロスプラットフォーム • CGAffineTransform 実践入門 • 2017/5/27 Cocoa Study at Matudo #6 • 最小限データーベースその2 • 最小限データーベースその3
  • 12. 振り返り: 2017/6 • 2017/6/5 Cocoa勉強会 BUKURO.swift #12 • 2017/6/16 MOSA BUKURO.swift #10 【WWDC2017特集!】 • WWDC2017 • WWDC2017レポート • Wikipediaを用いた検索キーワードのカテゴリ連想拡張 • 渦巻き • 2017/6/24 関東swift勉強会2017-6 • swiftによるアプリケーション開発-入門編 • 計算機プログラムの構造と解釈(SICP) • swiftでハノイの塔 • swiftで三段論法 • swiftで最小限タートルグラフィック
  • 13. 振り返り: 2017/7 • 2017/7/3 Cocoa勉強会 BUKURO.swift #13 • Machスレッドスタック • Swiftで8人の女王 • swiftでアナグラム • 2017/7/14 MOSA BUKURO.swift #11 • swiftによるアプリケーション開発-入門編 • 人工知能を実装する 意味ネットワーク • 2017/7/22 関東swift勉強会2017-7 • swiftで迷路の脱出 • swiftで迷路の作成 • PlaygroundでStoryboardの試作 • 空配列
  • 14. 振り返り: 2017/9 • 2017/9/4 Cocoa勉強会 BUKURO.swift #14 • Swiftによるアプリケーション開発:入門編 • AS.Parallel • Cross-platformプロジェクト • Objective-Cで書いていたライブラリをSwiftへ移植中に得られた知見など色々 • [macOS]Day One Classic 書類 • 2017/9/13 MOSA BUKURO.swift #12 • Swiftによるアプリケーション開発:入門編 • Designing for iPhone X • Building Apps for iPhone X • safeAreaLayoutGuide • 2017/9/23 関東swift勉強会2017-9 • Control Enablerのswiftへの移植 • NSMenuItemのショートカットキー表記方式の変更 • Apple Technology Lab. (よろず相談室)
  • 15. 振り返り: 2017/10 • 2017/10/02 Cocoa勉強会 BUKURO.swift #15 • 日記をお空に預ける! • 消費者スペクトラムとプロパガンダ • macOSのFinder • Unicodeの正規化
  • 16. How does Apple File System handle filenames? APFS accepts only valid UTF-8 encoded filenames for creation, and preserves both case and normalization of the filename on disk in all variants. APFS, like HFS+, is case-sensitive on iOS and is available in case-sensitive and case-insensitive variants on macOS, with case-insensitive being the default. In macOS High Sierra, APFS is normalization-insensitive in both the case-insensitive and case-sensitive variants, using a hash-based native normalization scheme. In iOS 11, APFS is normalization-insensitive as well, using either a native normalization scheme (erase restores only) or runtime normalization scheme (upgrades from previous versions). Runtime normalization will also be available in iOS 10.3.3 and macOS Sierra 10.12.6. Being normalization- insensitive ensures that normalization variants of a filename cannot be created in the same directory, and that a filename can be found with any of its normalization variants. This means that developers don’t need to do any additional work to ensure correct normalization behavior in these versions of macOS and iOS. Some differences between how APFS and HFS+ handle filenames include the following: • APFS implements normalization and case insensitivity according to the Unicode 9.0 standard; this enables APFS to support a wider range of languages for these features than HFS+, which is based on Unicode 3.2. • APFS preserves the normalization of the filename and uses hashes of the normalized form of the filename to provide normalization insensitivity, whereas HFS+ stores the normalized form of the filename on disk to provide normalization insensitivity. • Calling readdir(2) on a directory in APFS returns filenames in hash order, whereas HFS+ returns filenames in lexicographical order. • While both filesystems expect filenames to be encoded in UTF-8, APFS stores filenames on disk in UTF-8 encoding, whereas HFS stores filenames on disk in UTF-16 encoding. • APFS doesn’t allow files to be created with filenames that contain unassigned codepoints in the Unicode 9.0 standard, whereas HFS+ does. In iOS 10.3 and in the case-sensitive variant of the developer preview of APFS in macOS Sierra, APFS is normalization-sensitive. For these versions, developers should be aware of behavior differences between normalization sensitivity and insensitivity that may arise when a device upgrades macOS or iOS and migrates the filesystem from HFS+ to APFS. For example, attempting to create a file using one normalization behavior and then opening that file using another normalization behavior may result in ENOENT, or “File Not Found” errors. Additionally, storing filenames externally, such as in the defaults database, Core Data, or iCloud storage may cause problems if the normalization scheme of the filename being stored is different from what exists on disk. To avoid introducing bugs in your code with mismatched Unicode normalization (for iOS 10.3.0, 10.3.1 and 10.3.2) in filenames, do the following: • Use high-level Foundation APIs such as NSFileManager and NSURL when interacting with the filesystem. • Use the fileSystemRepresentation property of NSURL objects when creating and opening files with lower-level filesystem APIs such as POSIX open(2), or when storing filenames externally from the filesystem.
  • 17. 予定 • 2017/10/20 Swift勉強会 Developer’s Tech. Lab. • アウトライプロセッサ進捗会議 • お空に日記を預ける • Developer's Tech Lab • iOSDC 2017の参加報告 • Cocoa勉強会 MOSA 合同勉強会 BUKURO.swift 2017-11
  • 18. keep a diary • 発端 • どう実装するか? • Day Oneのデータ形式を探る • CloudKit • Cross-platform プロジェクト • macOSアプリケーション • keep a diary
  • 20. • Day Oneという日記アプリを利用しています。 • 種類が増えて、今後が不安。 • Day One Classic 売り切り。発表者は、これのユーザ。新規購入不可。 • Day One 2.0 別アプリとしてリリース。新規購入不可。 • Day One Premium/Plus/Basic 定額制プラン。
  • 21. Sync • Day One 独自サーバ。 • iCloud 探せばデータを見つけることは出来るが、見えないと考 えるべきか。 ~/Library/Mobile Documents/*~com~dayoneapp~dayone/Documents/Journal_dayone • Dropbox データは見える場所。 ~/Dropbox/アプリ/Day One/Journal.dayone
  • 22.
  • 23. Day One +- Journal.dayone +- entries | 1A573C0B559248A991C4AC0F0EA8B1E7.doentry | 1FB23D983DF341079532B0F9381BCFCC.doentry | : +- photos 1A573C0B559248A991C4AC0F0EA8B1E7.jpg 2D37E853754E43EFBA0FFBAEEB6A8B40.jpg :
  • 24. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Activity</key> <string>Automotive</string> <key>Creation Date</key> <date>2016-07-18T08:51:34Z</date> <key>Creator</key> <dict> <key>Device Agent</key> <string>iPhone/iPhone7,2</string> <key>Generation Date</key> <date>2016-07-18T08:51:34Z</date> <key>Host Name</key> <string>iPhone6GB128Gold</string> <key>OS Agent</key> <string>iOS/9.3.2</string> <key>Software Agent</key> <string>Day One iOS/1.17.9</string> </dict> <key>Entry Text</key> <string>AT教習のゼッケン 下で待つ。準備なし</string> :
  • 27. データは? • ローカルに保持 • 独自サーバ • クラウドサービス • iCloud • Firebase
  • 29. entriesとphotos • entriesディレクトリ 一つの日記の本体の情報。 • 1A573C0B559248A991C4AC0F0EA8B1E7.doentry ユニークIDがファイル名。 photosとの対応は、ユニークIDで。 • photosディレクトリ 写真。 • 1A573C0B559248A991C4AC0F0EA8B1E7.jpg ユニークIDがファイル名。 entriesとの対応は、ユニークIDで。
  • 31. @IBAction func performDumpDayOneClassic(sender: AnyObject) { let openPanel = NSOpenPanel() openPanel.allowsMultipleSelection = false openPanel.canChooseDirectories = true openPanel.canCreateDirectories = false openPanel.canChooseFiles = false openPanel.begin { (result) -> Void in if result == NSFileHandlingPanelOKButton { let inst = KeepADiary() inst.dump(url: openPanel.urls[0]) } } }
  • 32. class KeepADiary { public func dump(url: URL) -> Void { print(url) var entriesUrl = url entriesUrl.appendPathComponent("entries", isDirectory: true) print(entriesUrl) var fileList: [String] { do { return try FileManager.default.contentsOfDirectory(atPath: entriesUrl.path) } catch { return [] } } for fileName in fileList { var fileUrl = entriesUrl fileUrl.appendPathComponent(fileName) let plistXML: NSData = FileManager.default.contents(atPath: fileUrl.path)! as NSData let temp = try! PropertyListSerialization.propertyList(from:plistXML as Data, options: [], format: nil) as! [String:Any] print(temp) } } }
  • 33. ["Entry Text": AT教習のゼッケン 下で待つ。準備なし, "UUID": 38906447EE98439F9854DA354D9E95FA, "Step Count": 6909, "Creator": { "Device Agent" = "iPhone/iPhone7,2"; "Generation Date" = "2016-07-18 08:51:34 +0000"; "Host Name" = iPhone6GB128Gold; "OS Agent" = "iOS/9.3.2"; "Software Agent" = "Day One iOS/1.17.9"; }, "Ignore Step Count": 1, "Location": { "Administrative Area" = "U57fcU7389U770c"; Country = "U65e5U672c"; Latitude = "35.79399523328598"; Locality = "U548cU5149U5e02"; Longitude = "139.637718340512"; "Place Name" = "U4e0bU65b0U50095U4e01U76ee27U756a1U53f7"; Region = { Center = { Latitude = "35.79412019872211"; Longitude = "139.6382801"; }; Radius = "70.86956812177714"; }; }, "Starred": 0, "Time Zone": Asia/Tokyo, "Activity": Automotive, "Weather": { Celsius = 32; Description = "Partly Cloudy"; Fahrenheit = 90; IconName = "fair.png"; "Pressure MB" = 1005; "Relative Humidity" = 55; Service = HAMweather; "Sunrise Date" = "2016-07-17 19:39:47 +0000"; "Sunset Date" = "2016-07-18 09:56:36 +0000"; "Wind Bearing" = 180; "Wind Chill Celsius" = 32; "Wind Speed KPH" = 15; }, "Creation Date": 2016-07-18 08:51:34 +0000]
  • 36. どれくらい使える? • 10 GB Asset storage • 100 MB Database storage • 2 GB Data transfer • 40 Requests per second • Overage Fees • Asset storage $0.03/GB • Database storage $3.00/GB • Data transfer $0.10/GB • Requests per sec $100 per 10 requests
  • 40. フィールド型 クラス Description Asset CKAsset 大容量ファイルをレコードに 関連づけて別個に保存 Bytes NSData バイトバッファをレコードに 保存するためのラッパー Date/Time NSDate 時間軸上のある一時点 Double NSNumber 倍精度浮動小数点数 Int(64) NSNumber 整数 Location CLLocation 地理的座標(経緯度)および 高度 Reference CKReference あるオブジェクトから別のオ ブジェクトを参照するリレー ション String NSString 書き換え不可のテキスト文字 列 List NSArray 上記いずれかのフィールド型 の配列
  • 44.
  • 51. Storyboardを使用 • Storyboard使用を選択すると、生成されるプロジェクトの 構成が最新のものになる。 • 例えば、Document-based Applicationの場合、以前は、 DocumentクラスにOutletとActionが。 Storyboard使用を選択すると、Cocoa touchの UIViewControllerに似たNSViewControllerが利用される。
  • 53. コードで設定 class ViewController: NSViewController { override func awakeFromNib() { self.view.window?.initialFirstResponder = self.nameField } } この問題、BUKURO.swiftで議論した際に、汎用的な方法の提案もありましたが、 複雑だったので、この方法がベターかなと思っています。 http://www.mindto01s.com/2017/02/26/e97ab989_f357_44c7_8810_21ce715d9d2b.html いい方法をご存知な方がいらっしゃいましたら、是非。
  • 55. コンテナ コンテナID デフォルトコンテナ let container: CKContainer = CKContainer.default() カスタムコンテナ let container: CKContainer = CKContainer(identifier: “iCloud.com.example.KeepADiary”)
  • 56. データベース 公開データベース let publicDatabase: CKDatabase = container.publicCloudDatabase 非公開データベース let privateDatabase: CKDatabase = container.privateCloudDatabase 共有データベース let sharedDatabase: CKDatabase = container.sharedCloudDatabase
  • 58. アカウントの確認 CKContainer.default().accountStatus(completionHandler: { accountStatus, error in if error != nil { print(error ?? "") } else if accountStatus == .noAccount { let alert = UIAlertController.init(title: "Sign in to iCloud", message: "Sign in to your iCloud account to write records.On the Home screen, launch Settings, tap iCloud, and enter your Apple ID.Turn iCloud Drive on.If you don't have an iCloud account, tap Create a new Apple ID.", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Okay", style: .cancel, handler: nil)) self.present(alert, animated: true, completion: nil) } else { print(“Insert your just-in-time schema code here”) } })
  • 61. • 開発環境 • スキーマ生成 • レコード追加 • スキーマを配備 • 開発環境から実稼働環境へスキーマをコピー • 実稼働環境 • レコード追加
  • 62. 追加 /* レコードIDを生成 */ let uuidString = UUID.init().uuidString let diaryRecordID: CKRecordID = CKRecordID(recordName: uuidString) /* レコード・オブジェクトを生成 */ let diaryRecord: CKRecord = CKRecord(recordType: "Diary", recordID: diaryRecordID) /* レコードのフィールドを設定 */ diaryRecord["UUID"] = uuidString as CKRecordValue diaryRecord.setObject("本文です。" as NSString, forKey: "EntryText") diaryRecord.setObject(Date() as NSDate, forKey: "DiaryDate") /* レコードを保存する */ database.save(diaryRecord, completionHandler: { (record, error) in if error != nil { print(error) } else { print(record) } })
  • 63. 更新 /* レコードIDからレコードを取得する */ database.fetch(withRecordID: diaryRecordID, completionHandler: { (record, error) in if error != nil { print(error) } else { /* 値を更新する */ record.setObject("本文を更新します。" as NSString, forKey: "EntryText") /* レコードを保存する */ database.save(diaryRecord, completionHandler: { (record, error) in if error != nil { print(error) } else { print(record) } }) } })
  • 64. 削除 /* 追加で使ったレコードIDで取得 */ database.delete(withRecordID: diaryRecordID, completionHandler: { (recordId, error) in if error != nil { print(error) } else { print(recordId) } })
  • 65. 検索 /* 検索条件 */ let now = Date() var calendar = Calendar.current calendar.locale = Locale(identifier: "ja") var component = calendar.dateComponents([.year, .month, .day], from: now) component.hour = 0 component.minute = 0 component.second = 0 let beginDate: NSDate = calendar.date(from:component)! as NSDate let endDate: NSDate = NSDate(timeInterval: (60.0 * 60.0 * 24.0), since: beginDate as Date) let dateFormatter = DateFormatter() dateFormatter.locale = Locale(identifier: "ja_JP") dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss" let beginDateString = dateFormatter.string(from: beginDate as Date) let endDateString = dateFormatter.string(from: endDate as Date) let predicate = NSPredicate(format: "DiaryDate != %@", NSDate()) let query : CKQuery = CKQuery(recordType: "Diary", predicate: predicate) /* 検索する */ database.perform(query, inZoneWith: nil, completionHandler: { results, error in if error != nil { print(error) } else { print(results) /* 検索した結果 */ for record in results! { /* 削除する */ database.delete(withRecordID: record.recordID, completionHandler: { (recordId, error) in if error != nil { print(error) } else { print(recordId) } }) } } })
  • 68. サブスクリプション保存 /* 条件 */ let predicate = NSPredicate(format: "TRUEPREDICATE") let subscription = CKSubscription(recordType: "Diary", predicate: predicate, options: [CKSubscriptionOptions.firesOnRecordCreation, CKSubscriptionOptions.firesOnRecordUpdate]) /* 通知 */ subscription.notificationInfo = CKNotificationInfo() subscription.notificationInfo?.alertBody = "Item has been added." subscription.notificationInfo?.alertLocalizationKey = "Diary record has changed!" subscription.notificationInfo?.shouldBadge = true /* 保存 */ database.save(subscription, completionHandler: { subscription, error in if error != nil { print(error) } else { print(subscription) } })
  • 69.
  • 70. macOS func applicationDidFinishLaunching(_ aNotification: Notification) { NSApplication.shared().registerForRemoteNotifications(matching: [.alert, .sound]) } func application(_ application: NSApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { } func application(_ application: NSApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { } func application(_ application: NSApplication, didReceiveRemoteNotification userInfo: [String : Any]) { }
  • 71. iOS func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { if #available(iOS 10.0, *) { let center = UNUserNotificationCenter.current() center.delegate = self center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in if error != nil { return } if granted { DispatchQueue.main.async { application.registerForRemoteNotifications() } } else { } } } else { let settings = UIUserNotificationSettings(types: UIUserNotificationType.alert, categories:nil) application.registerUserNotificationSettings(settings) application.registerForRemoteNotifications() } return true }
  • 72. func application(_ application: UIApplication, didRegister notificationSettings: UIUserNotificationSettings) { } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { } func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let cloudKitNotification: CKNotification = CKNotification(fromRemoteNotificationDictionary: userInfo) let alertBody = cloudKitNotification.alertBody print("alertBody: (String(describing: alertBody))") if cloudKitNotification.notificationType == .query { let queryNotification = cloudKitNotification as! CKQueryNotification let recordID = queryNotification.recordID } } func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { } /* 前面で通知受信 */ @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { } /* 通知を選択 */ @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { completionHandler() }
  • 74.
  • 75. DEMO

Notas do Editor

  1. 勉強会の意義。 発表することによって理解が深まる。 他人の成果を吸収する。 コミュティづくり。営業の側面もある。 アクティブメンバーの有志で非公開グループが運営されている。 待ってます。
  2. 申し込みは六十数名。 三十数名キャンセル。
  3. 十数名。
  4. Day One Classicの場合。 三つの同期方法を切り替えてゆくと、三つとも同じ内容になった。 Dropbox上のデータが移行に使えると判断。
  5. UUIDがエンティティのファイル名。画像は別ファイルでUUIDがファイル名。 全体を管理するファイルは用意していない。多分、起動時になめて、動的な目次を持っている?
  6. 文書の書式はプロパティリスト。
  7. *ローカルに保持 **ユーザに見せても、見せられた方も困る。 **iCloudのバックアップで同期できる。 *独自サーバ **サーバとそこで動くプログラムが必要。 *クラウドサービス **iCloud    特別な契約は不要。 ***iCloud Drive ****ユーザにファイルを見せても、見せられた方も困る。 ***CloudKit ****異なるOSや端末で同期できる。 **Firebase    Googleの方針変更が怖い ***Firebase Realtime Database ***Cloud Storage
  8. 日記用途だと、100MBは辛い?
  9. データはコンテナに格納される。 別の開発者のコンテナにはアクセスできない。 自分が製作したアプリで、自分のコンテナを共有することはできる。 CloudKitを有効にすると、バンドルIDと同じ名前のコンテナが生成される。 一度生成したコンテナは削除できない。 macOSとiOSで同じバンドルIDを設定することができない。
  10. SpriteKitの雛形がCross-platformなので、これの構成を参考にした。
  11. 空のプロジェクトを生成する。
  12. macOSのCocoa Applicationをターゲットとして追加する。
  13. ターゲット名をmacOSとするのがオススメ。 例えば、プロダクト名のKeepADiaryを設定すると、iOSターゲットとかぶる。 また、KeepADiaryForMacOSとすると、長くなってしまい、操作に支障が出る。
  14. CapabilitiesでCloudKitを有効にすると、このバンドルIDでコンテナが生成される。 生成されたコンテナは削除できないので、じっくりと検討して、バンドルIDを決めること。
  15. 同じファイル名にすると、Xcodeは正しく使い分けてくれない。
  16. サンプルのコードを見ていきます。
  17. 公開データベースの内容は、CloudKit Dashboardで確認でき編集できるが、非公開データベースだとできないので、最初は公開データベースで試行錯誤するのがオススメ。
  18. 非公開データベースは、ユーザー毎。
  19. iCloudアカウントでサインイン。
  20. 試験用アカウントのデータは、試験用アカウントでないとアクセスできないので、そのApple IDを開発アカウントにして、ダッシュボードで表示。
  21. 実稼働環境で利用されるようになると、スキーマは変更できない。
  22. エンティティの、あるフィールドの値変更は、そのエンティティ全体の更新となる。
  23. ここで試行錯誤できる。
  24. データベースに対して保存ということなので、上書き時はエラーのようです。
  25. 通知を駆使すれば、常に同じ値を保つこともできるかもしれないが、通知は必ず届くものでないのと、リソースの制限があることを考えると、同期ボタンを押すと、お空にデータを預ける、そして、同期されるという利用がいいのかも。