プッシュ通知 証明書 更新 手順
- プッシュ通知(APNs 証明書)の更新メモ
- Certificates, Identifiers & Profilesにて、証明書を更新する
- サーバーへ新しい証明書を配布する
更新手順
No. | 内容 | Development | Product | 備考 |
---|---|---|---|---|
0 | KeyChain(開発環境)->証明局に証明書(CertificateSigningRequest.certSigningRequest)を要求 | ◯ | ◯ | Development・Product共通、複数のアプリで共通で使う |
1 | Deveropper Center(Safari)->Certificates の作成 | ◯ | ◯ | アプリごと、No.0で要求した「CertificateSigningRequest.certSigningRequest」ファイルを使う |
2 | Deveropper Center(Safari)->Provisioning Profiles の更新 | △ | △ | アプリごとに対応必要。アプリのバージョンアップ時に必要 |
2 | Xcode(開発環境)->Buid Setting->Provisioning Profile のダウンロード | △ | △ | アプリごとに対応必要。アプリのバージョンアップ時に必要。新しくProvisioning Profileは作成されるが、特に更新しなくてもプッシュ通知は受信できる |
3 | KeyChain(開発環境)->p12ファイルを書き出す | ◯ | ◯ | アプリごとに対応必要。鍵ではなく証明書で書きだす |
- No.0
- No.3
手順
- Certificates の削除(更新前のCertificatesの有効期限が切れていない場合)・作成(Apple Push Notification service SSL)→作成後ダウンロード
- Provisioning Profiles の更新
- p12ファイルを書き出す(キーチェーンより「1.Certificates」で作成した証明書を選択し書き出し)
- p12ファイルの鍵を書き出す(キーチェーンより「1.Certificates」で作成した鍵を選択し書き出し)(サーバー側で必要であれば)
- p12をpemファイルへ変換する(サーバー側で必要であれば)
- サーバへp12またはpemファイルを配布
p12からpemへの変換コマンド
openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12 openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12 openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem
参考URL
プッシュ通知文字数(おまけ)
表示箇所 | iOS6 | iOS7 |
---|---|---|
ステータスバー(画面上部) | 全角22文字、半角40文字 | 全角35文字、半角64文字 |
ダイアログ(ロック画面に表示) | 全角57文字、半角108文字 | 全角58文字、半角114文字 |
通知センター | 全角43文字、半角154文字 | 全角58文字、半角114文字 |
iOS 開発 カメラ
- iOS(iPhone,iPad)のカメラ、写真の開発時のメモです
- カメラを起動したり、フォトアルバムから写真を選択してアプリに渡したりできます
- アプリで高解像度(1MB以上)の写真や画像を大量に表示するとメモリ不足でクラッシュします(100KB位まで解像度を下げるのがオススメ)
写真やムービーの撮影
- 2つのテクノロジー
- UIImagePickerController:カスタマイズ可能な基本的なユーザイン ターフェイス
- AVFoundation FW:UIKitと一緒に使用し完全にカスタマイズした静止画像やムービーのキャプチャ機能をアプリケーション向けに作成することができる
- どちらを採用する場合も、Assets Library FWを使用して、メディアのメタデータ(GPS位置情報など)を管理できる
保存済みの写真やムービーをユーザのフォトアルバムから選択
- 2つのテクノロジー
- UIImagePickerController:基本的なユーザインターフェイスを使用してユーザがフォトライブラリからメディアを選択できる
- Assets Library FW:UIKitと一緒に使用し完全にカスタマイズした写真やムービーのブラウザを作成できる
Image Picker Controllerを利用した写真やムービーの撮影
- カメラインターフェイス(UIImagePickerControllerクラスのインスタンス)をインスタンス化してモーダルモードで表示
- システムは、このカメラインターフェイスとユーザとのやり取りを管理(ユーザが写真やムービーを撮影、またはキャンセル)
- システムはImagePickerControllerのデリゲートオブジェクトのメソッドを呼び出し今度は、このメソッドがユーザの操作(たとえば、新規の写真を「カメラロール(Camera Roll)」アルバムに保存する)の結果を処理。(カメラインターフェイスを消去するのもデリゲート)
参考URL
iOS 保存領域 比較 〜NSUserDefaults, CoreData,Keychain,等〜
アプリ容量の制限について
アプリのストレージの最大容量は、おそらく制限なし(20M以上だとDL時にwifiが必要となる)
保存領域について
種類 | 説明 |
---|---|
アプリ領域 | アプリが自由に使え、他のアプリから参照できない領域 |
OS領域 | OSが使用するデータが保存されており、基本的にアプリから参照できない領域 |
共有領域 | メディアライブラリ(写真、音楽等)やイベント(カレンダー等)、連絡先は全てのアプリから参照することができる領域(アプリからできる内容は、書き込みと閲覧のみ) |
永続データの復元(比較)について
- 復元時にデータも復元するには、外部サーバーにデータを保存するかiCloudを利用しないと殆ど初期化される
- 保存領域が共有領域であれば、アプリ自体にiCloudの処理をいれなくても、共有領域のデータは復元できそう
種類 | 復元可否 | 備考 |
---|---|---|
アーカイブ | ☓ | オブジェクトをバイナリ形式に変換してからファイルに永続化(一番基礎的な技術) |
プロパティリスト | - | ユーザーが変更しないもの(定数)、plist |
NSUserDefaults | ☓ | ユーザーが変更するもの |
CoreData | ☓ | データ量多い |
Keychain | ◯ | パスワードや個人情報(暗号化されていない場合復元されない) |
アプリ内にDBとして保存したい場合realmがオススメです。(日本語のドキュメントもしっかりしていて、組み込みも簡単で、ブラウザも用意されています)
参考URL
Xcode デバッグ 値変更
概要
- Xcodeでデバック中に、変数の値や関数の実行する方法の説明です
- デバックエリアでLLDBコマンドを実行して確認します
- 値の変更はデバックエリアで「po 変数名 = 値」で変更できます
LLDBとは、Mac OS X上のXcodeでデフォルトのデバッガで、デスクトップとiOSデバイスとシミュレータ上のC、のObjective-CとC++、Swiftのデバッグをサポートしています。
- 参考URL:About LLDB and Xcode
実行方法
- デバックエリアを表示(メニューバー->View->Debug Area->Show Debug Area)
- 止めたいところにブレークポイント設定
- プログラムの実行
- デバックエリアでLLDBコマンドの実行
実行例
- 変数の値や関数の実行する場合は、「(lldb) po 変数名 または 関数名」を実行します
「po self.dynamicType」でプロジェクト名とクラス名を表示している例
LLDBコマンド一覧(一部抜粋)
省略形 | 内容 | 正式名 |
---|---|---|
po | 式の評価(poはオブジェクト、pは基本型も可) | expression -o -- |
h | ヘルプの表示 | help |
s | ステップイン | thread step-in |
c | 続きを実行(次のbreakPointまで) | process continue |
br l | breakPointを一覧表示 | breakpoint list |
bt | 現スレッドのバックトレース情報を表示 | thread backtrace |
iOS AdMob 実装 方法
AdMob SDKの導入方法や手順が書いてあります。(iPhoneやiPadが対象)
やりたいこと
iOSアプリにAdMobのバナー広告を表示させたい。
(AdMobとは、Googleが運営するアプリ専門のアドネットワーク。日本国内の収益性は微妙だが、海外の広告案件が豊富。)
流れ
- AdMobに登録
- アプリにSDK組込
- 広告ユニットIDの作成
細かい部分はAdMobのスターターズガイドがしっかりしているので、その通り進めば多分大丈夫だと思います。
1. AdMobに登録
AdMobのサイトでユーザー登録をします。
「Sign Up」ボタン押下後適当に個人情報を入力して進みます。
(全文英語ですが、一般的なユーザー登録なので英語読めなくても何とかなるはず。。。)
2. アプリにSDK組込
今回は、CocoaPodsを利用してSDKを組込みます。
CocoaPodsの利用方法がわからない方はこちら。
2-1. Podfileの作成・SDKインストール
ターミナルを起動しPodfileを作成しSDKをインストールします。
$ vi Podfile # Podfileの作成 platform :ios, '8.0' pod 'Google-Mobile-Ads-SDK', '~> 7.0' $ pod install # SDKインストール Analyzing dependencies CocoaPods 0.36.0.beta.2 is available. To update use: `gem install cocoapods --pre` [!] This is a test version we'd love you to try. For more information see http://blog.cocoapods.org and the CHANGELOG for this version http://git.io/BaH8pQ. Downloading dependencies Installing Google-Mobile-Ads-SDK (7.0.0) Generating Pods project Integrating client project [!] From now on use `BannerExample.xcworkspace`. $ # 「プロジェクト名.xcworkspace」が作成されていればインストール完了!
2-2. AdMobを実装
とりあえずSDKが読み込めているか試してみましょう
# swift // Bridging-Header.h @import GoogleMobileAds // BannerExampleViewController.swift class BannerExampleViewController { override func viewDidLoad() { super.viewDidLoad() println("Google Mobile Ads SDK version:\(GADRequest.sdkVersion())") } }
# Objective-C // BannerExampleViewController.m @import GoogleMobileAds; #import "ViewController.h" @implementation BannerExampleViewController - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"Google Mobile Ads SDK version: %@", [GADRequest sdkVersion]); } @end
次に、バナーを作成し画面表示されたらOKです。
広告ユニットIDは、「3. 広告ユニットIDの作成」で作成し自分のIDに変更しましょう
# swift // BannerExampleViewController.swift // GADBannerViewDelegateを追加 class BannerExampleViewController, GADBannerViewDelegate { override func viewDidLoad() { super.viewDidLoad() println("Google Mobile Ads SDK version:\(GADRequest.sdkVersion())") var bannerView: GADBannerView = GADBannerView() bannerView = GADBannerView(adSize:kGADAdSizeBanner) // 広告ユニットIDを指定する(3. 広告ユニットIDの作成参照) bannerView.adUnitID = "ca-app-pub-3940256099942544/2934735716" bannerView.delegate = self bannerView.rootViewController = self self.view.addSubview(bannerView) bannerView.loadRequest(GADRequest()) } }
# Objective-C // BannerExampleViewController.h // SDK から GADBannerView の定義をインポートする #import "GADBannerView.h" @interface BannerExampleViewController : UIViewController { // インスタンス変数として 1 つ宣言する GADBannerView *bannerView_; } @end // BannerExampleViewController.m #import "BannerExampleViewController.h" @implementation BannerExampleViewController - (void)viewDidLoad { [super viewDidLoad]; // 画面上部に標準サイズのビューを作成する // 利用可能な広告サイズの定数値は GADAdSize.h で説明されている bannerView_ = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner]; // 広告ユニットIDを指定する(3. 広告ユニットIDの作成参照) bannerView_.adUnitID = MY_BANNER_UNIT_ID; // ユーザーに広告を表示した場所に後で復元する UIViewController をランタイムに知らせて // ビュー階層に追加する bannerView_.rootViewController = self; [self.view addSubview:bannerView_]; // 一般的なリクエストを行って広告を読み込む [bannerView_ loadRequest:[GADRequest request]]; } - (void)dealloc { // プロジェクトで ARC を使用している場合は bannerView_ を解放しない [bannerView_ release]; [super dealloc]; } @end
広告のサイズやターゲットの設定はこちら
3. 広告ユニットIDの作成
- AdMob管理画面に「1. AdMobに登録」で作成したアカウントでログイン
- 広告ユニットIDの作成
- 「収益化->新しいアプリの収益化」ページまで移動
- アプリを追加
- 広告フォーマットの選択と広告ユニット名の設定
- 表示の設定方法
- 完了ボタン押下
- 広告ユニットIDをアプリに設定
- 「収益化->すべてのアプリ」ページまで移動
自分が追加したアプリの「広告ID」が発行されています
「2-2. AdMobを実装」で設定していた「広告ID」を自分の「広告ID」に変更しましょう
- 表示と計測の確認
- アプリを起動し広告が表示されているか確認しましょう(広告IDを変更した場合、よく見かける一般的な広告に差し替わっていると思います。)
- 広告を押下し外部サービスが起動することを確認しましょう
- 管理画面上で計測が正しくとれていることを確認しましょう(リアルタイムに反映されないので時間を置いて確認しましょう(私の場合1時間位で反映されていました))
以上で全て終了です。お疲れ様でした!
CoreData の 関連図 と 手順
概要
- CoreDataの知識が曖昧だったのでクラスの関連図や手順についてのメモです(細かい実装方法や使い方は他の参考サイトを参考にしてください)
- DB自体初めての人には登場人物が多くて難易度高めな気がします
- 参考資料:iOS Core Data チュートリアル
関連図
Core Data | RDB |
---|---|
NSManagedObjectModel | DB |
Entity | Table |
NSManagedObject | Record |
Attribute | Column |
- NSManagedObjectModel:管理オブジェクトモデル(データベース)
- NSManagedObject:管理オブジェクト(レコード)
- NSManagedObjectContext:管理オブジェクトコンテキスト(オブジェクト空間、管理オブジェクトのコレクションを管理する)
- NSPersistentStoreCoordinator:永続ストアコーディネータ(ストアを管理して、管理オブジェクトコンテキストに、1つ の統一されたストアのファサードを提供する)
作成手順
- プロジェクトの準備
- テーブルの作成
- テーブルにデータ追加
- NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
- NSEntityDescriptionでテーブルの指定
- NSManagedObjectで値の受け渡し
- テーブルからデータ取得
- NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
- NSFetchRequestでテーブルの指定
import CoreData class MyClass : UIViewController { // 3. テーブルにデータ追加 @IBAction func insert(sender: AnyObject) { let appDelegate: AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate let managedContext: NSManagedObjectContext = appDelegate.managedObjectContext! /* Create new ManagedObject */ let entity = NSEntityDescription.entityForName("テーブル名", inManagedObjectContext: managedContext) let personObject = NSManagedObject(entity: entity!, insertIntoManagedObjectContext: managedContext) // let myPersonObject = NSManagedObjectを継承したカスタムクラス(entity: entity!, insertIntoManagedObjectContext: managedContext) // 自分で作成したカスタムを使う場合 /* Set the name attribute using key-value coding */ personObject.setValue("値", forKey: "カラム名") // myPersonObject.カラム名 = "値" // 自分で作成したカスタムを使う場合 /* Error handling */ var error: NSError? if !managedContext.save(&error) { println("Could not save \(error), \(error?.userInfo)") } println("object saved") } // 4. テーブルからデータ取得 @IBAction func selectAll(sender: AnyObject) { /* Get ManagedObjectContext from AppDelegate */ let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate let manageContext:NSManagedObjectContext = appDelegate.managedObjectContext! /* Set search conditions */ let fetchRequest = NSFetchRequest(entityName: "テーブル名") var error: NSError? /* Get result array from ManagedObjectContext */ let fetchResults = manageContext.executeFetchRequest(fetchRequest, error: &error) if let results: Array = fetchResults { for obj:AnyObject in results { let name:String? = obj.valueForKey("カラム名") as? String println(name) } println(results.count) } else { println("Could not fetch \(error) , \(error!.userInfo)") } }
まとめ
- xcdatamodeldでテーブル情報(テーブル名、カラム名、カラム属性、リレーション等)を設定する
- NSManagedObjectを継承したクラスをテーブルのカラム名を書いて、テーブルとの受け渡し用の入れ物に使う
- NSManagedObjectContextでDML操作(レコードの追加・検索・更新・削除など)
- NSEntityDescriptionでテーブルの指定
- データの保存場所を確認したい場合、applicationDocumentsDirectoryを見る
- 個人的には、FMDBのライブラリを使って直接SQLでデータをやりとりした方が自由で楽なきがします。。。
参考サイト
Swift 文法
概要
Swiftの入門的な部分をまとめてみました。
初心者向けに文法の部分を中心に書きました。
また、SwiftはObjective-Cより直感的ですっきりしているので、
iOSの開発を始めたい方はSwiftでの開発がオススメです。
目次
- 変数宣言(var,let)
- 型(Int,String...)
- 文字列操作
- 配列(Array)
- 辞書(Dictionary)
- タプル(Tuple)
- 繰り返し(for,while)
- 条件分岐(if,switch)
- 関数(func)
- クラスと構造体(class,struct)
- オプショナル型(Optional)
- おすすめの勉強法
1. 変数宣言(var,let)
- 変数の宣言には「var」と「let」がある
- varは、変数
- letは、定数
// 型を指定して生成 var varInt:Int // 宣言:var 変数名:型 varInt = 0 // 代入:変数名=値 var varInt2:Int = 0 // 宣言+代入:var 変数名:型=値 // 型推論(型を指定せずに生成) var varInt3 = 3 // 値からInt型の変数が生成される let pi = 3.14 // let pi2:Double // 宣言のみだとエラー(値の代入も宣言時に必要) // pi = 3.1415926 // 値を変更するとエラー
2. 型(Int,String...)
- マイナス値がない「UInt」型がある
- 添字などで使うと安全性が高まる
- 型指定なしの「Any」型がある
名前 | 説明 |
---|---|
Int | 整数型 |
UInt | 符号なし整数型 |
Float | 単精度少数型 |
Double | 倍精度少数型 |
Bool | 論理型 |
Charecter | 文字型 |
String | 文字列型 |
Any | 型指定なし |
AnyObject | 型指定なし(Classに限定) |
3. 文字列操作
- Stringの文字列は「+」で結合できる
- var str:String = "Hello"+"World"
- String内に処理の実行結果を代入する時は「\(処理)」で出来る
- var str:String = "1+1=\(1+1)"
4. 配列(Array)
// 型を指定 var varArray:[String] // 型推論(型を指定せずに生成) var varArray2 = ["var0", "var1", "var2"] // 空の配列 var varArray3 = [String]() var varArray4:[String] = Array() // 要素の追加 varArray2.append("var3")
5. 辞書(Dictionary)
// 型を指定 var varDict:Dictionary<String, String> // 型推論(型を指定せずに生成) var varDict2 = ["key0":"var0", "key1":"var1", "key2":"var2"] // 空の辞書 var varDict3 = Dictionary<String, String>() var varDict4:Dictionary<String, String> = Dictionary() // 要素の追加 varDict2["key3"] = "var3" // 値の取得 if let value = varDict2["key3"] { print(value) } // キーと値の取得例 for (dictName, dictValue) in varDict2 { print("キーは\(dictName)で、値は\(dictValue)です。") }
6. タプル(Tuple)
- タプルは複数の値をグループとして扱える(「x,y,width,height」や「name,age」とかをグループにしたい時に便利)
- タプル内の値は、異なる型の値を設定出来る
// 値を設定 let http404Error = (404, "Not Found") // 値を取得 print("The status code is \(http404Error.0)") // 他の変数に設定、取得 let (statusCode, statusMessage) = http404Error print("The status code is \(statusCode)") // 変数名をつけて設定、取得 let http200Status = (statusCode: 200, description: "OK") print("The status code is \(http200Status.statusCode)")
7. 繰り返し(for,while)
var roopArray = ["var0","var1","var2"] // for-in ループ for i in 0..<roopArray.count { print(roopArray[i]) } // 条件文付きforループ for var i = 0; i < roopArray.count; i++ { print(roopArray[i]) } // 代入forループ for value in roopArray { print("\(value)") } // while var roopIndex = 0 while roopIndex < roopArray.count { print(roopArray[roopIndex]) roopIndex++ }
8. 条件分岐(if,switch)
- ifの条件式に()は、書かなくてもOK
- switchは、breakを書かなくてもbreakする
- switchでも、条件が複数指定できる
- switchでも、細かい条件も指定できる
var varStr:String = "Hello World" // if if varStr != "" { print(varStr) } // if条件分岐+代入 var varStr2:String? if let varStr3 = varStr2 { print(varStr3) // varStr2がnilじゃない場合、varStr3にvarStr2を代入 } else { print("varStr2 is nil") } // if条件分岐+代入(三項演算子) その2 var varInt:Int = 1 var varStr3:String = (1 == varInt) ? "1" : "" // switch let vegetable = "red pepper" switch vegetable { case "celery": let vegetableComment = "Add some raisins and make ants on a log." case "cucumber", "watercress": // 複数条件 let vegetableComment = "That would make a good tea sandwich." case let x where x.hasSuffix("pepper"): // 細かい条件 let vegetableComment = "Is it a spicy \(x)?" default: let vegetableComment = "Everything tastes good in soup." }
9. 関数(func)
- 戻り値が複数設定出来る
// 関数を呼び出す // func 関数名(引数: 型) -> 戻り値 func square(num: Int) -> Int { return num * num } // 関数を使う print("計算結果は\(square(2))です") // 引数が複数の場合、引数を「,」区切りで宣言 func sum(num1: Int, num2: Int) -> Int { return num1 + num2 } // 戻りが複数の場合、戻り値を「,」区切りで宣言 func tax(num: Float) -> (five: Float, eight: Float) { return (five: num*1.05, eight: num*1.08) } var taxPrice = tax(100.0) // 複数の戻りを取り出す場合、「.」で指定する print("消費税5%だと、\(taxPrice.five)円です") print("消費税8%だと、\(taxPrice.eight)円です")
10. クラスと構造体(class,struct)
- クラスと構造体は、変数や関数等をまとめたプログラム
- クラスと構造体の違いは、「型」として性質(セマンティクス)が異なる
- クラス(class):参照型(コピーした時に、コピー元の値を変更するとコピー先も同じものを参照しているので値が変わる)
- 構造体(struct):値型(コピーしてもそれぞれにインスタンスされる)
クラス(class)
- 本格的に作る場合に向いている
- 継承を使える
- ARCが使える
- インスタンスのライフサイクルが長いもに向いている
- 例)UIColor,UIImage等(殆どがクラス)
class RectClass { // 変数 var x: Float = 0 var y: Float = 0 var width: Float = 0 var height: Float = 0 // 関数 func maxX() -> Float { return x+width } func maxY() -> Float { return y+height } } var rectC = RectClass() rectC.x = 0; rectC.width = 100; print("最大幅は\(rectC.maxX())です");
構造体(struct)
- 軽く作る場合に向いている
- プロパティ数が少ないもに向いている
- 例)CGRext,CGPoint,CGSize,Array等
struct RectStruct { // 変数 var x: Float = 0 var y: Float = 0 var width: Float = 0 var height: Float = 0 // 関数 func maxX() -> Float { return x+width } func maxY() -> Float { return y+height } } var rect0 = RectStruct() var rect1 = RectStruct(x:0.0, y:0.0, width:100.0,height:200.0) print("最大幅は\(rect1.maxX())です");
11. オプショナル型(Optional)
- オプショナル型
- 「?」をつける
- nilになる可能性があることを示している
- ラップされた状態
- 非オプショナル型
- 「!」をつける
- nilになる可能性がないことを示している
- アンラップされた状態
- オプショナル連鎖
- オプショナル型のメソッドや変数にアクセスする時は、オプショナルでアクセスする必要がある
// NSURLクラスでの例 NSURL.h var scheme: String? { get } var host: String? { get } var path: String? { get } var query: String? { get } MyClass var url = NSURL(string: "http://www.apple.com")! var host: String? = url.host // ラップ var host2: String = url.host! // アンラップ // var host3: String = url.query // クエリーを設定していないので実行時エラーになる(宣言元にラップ状態の明示が必要) var host3: String? = url.query // host.hasPrefix("www") // オプショナル連鎖(?が必要) host?.hasPrefix("www")
12. おすすめの勉強法
- Swift - Overview - Apple Developerをざっくり読む
- The Swift Programing Languageを読む(Swift言語ブログが日本語で解説されていて分かりやすい)
- A Swift Tour: Playground fileからSampleCodeをダウンロード(1.The Swift Programing Languageがplaygroundで閲覧出来る)
- 気になる点をplaygroundで書く
- Using Swift with Cocoa and Objective-Cを読む(CocoaのObjective-Cとswiftの違いが書かれている)
- オススメの書籍
文法もわかりやすく、rssアプリを作成出来ます。基礎の入門書としてオススメです。
他に参考になる資料(2015.12追記)
- Swift.org(Swiftのコミュニティーのためのポータルサイト)
- Swift2 メモ(try catch、Availability、guard、defer等)
- プログラミング言語 Swift(はてな教科書)←初心者にかなりオススメ
- Swift での iOS アプリ開発(はてな教科書)←初心者にかなりオススメ
- Awesome-Swift-Education(Swiftを学習するためのキュレーションリスト)
その他
- IBM Swift Sandbox(swiftがウェブプラザで動かせるサイト。マックを持ってないけど軽く触ってみたい人にオススメ。)