- 投稿日:2020-04-03T23:12:23+09:00
簡単にFirebase Storageに複数のファイルをアップロードする[swift]
概要
複数のファイルのアップロードを以下のように扱えるようにStorageUploaderを作りました。どれくらいアップロードされているのか監視もできます。
sample.swiftlet uploader = StorageUploader() // アップロードしたいファイルを追加 uploader.addFile(file:URL.init("video"), childRef:videoChildRef, metadata:videoMetadata) uploader.addData(data:imageData, childRef:imageChildRef, metadata:imageMetadata) // アップロード状況の監視 uploader.progressHandler = {completedCount, totalCount in print("\(100 * completedCount / totalCount)%") } // アップロード完了時の処理 uploader.completeHandler = { metadatas in print("complete") } // アップロード失敗時の処理 uploader.failureHandler = { error in // switch (StorageErrorCode(rawValue: error.code)!) { ... print("fail") } // アップロード開始 uploader.start()StorageUploaderのコード
StorageUploader.swiftimport Foundation import FirebaseStorage class StorageUploader { fileprivate struct SUComponent { var file:URL? var data:Data? var childRef:StorageReference? var metadata:StorageMetadata? var isComplete = false } var progressHandler: (_ completedUnitCount: Int64, _ totalUnitCount: Int64) -> Void = {_, _ in } var completeHandler: (_ metaDatas: [StorageMetadata]) -> Void = {_ in } var failureHandler: (_ error: NSError?) -> Void = {_ in } fileprivate var components = [SUComponent]() fileprivate var uploadTasks = [StorageUploadTask]() fileprivate var totalUnitCount:Int64 = 1 fileprivate var completedUnitCount:Int64 = 0 fileprivate var isfailure = false func addFile(file:URL, childRef:StorageReference, metadata:StorageMetadata) { var uploadInfo = SUComponent() uploadInfo.file = file uploadInfo.childRef = childRef uploadInfo.metadata = metadata components.append(uploadInfo) } func addData(data:Data, childRef:StorageReference, metadata:StorageMetadata) { var uploadInfo = SUComponent() uploadInfo.data = data uploadInfo.childRef = childRef uploadInfo.metadata = metadata components.append(uploadInfo) } func start() { prepareUpload() progressHandler(completedUnitCount, totalUnitCount) } fileprivate func prepareUpload() { components.forEach { uploadInfo in let childRef = uploadInfo.childRef let metadata = uploadInfo.metadata if let file = uploadInfo.file { guard let uploadTask = childRef?.putFile(from: file, metadata: metadata) else { return } observeStatus(uploadTask) } else if let data = uploadInfo.data { guard let uploadTask = childRef?.putData(data, metadata: metadata) else { return } observeStatus(uploadTask) } } } fileprivate func observeStatus(_ uploadTask: StorageUploadTask) { uploadTasks.append(uploadTask) uploadTask.observe(.progress) { snapshot in var currentCompletedUnitCount:Int64 = 0 var currentTotalUnitCount:Int64 = 0 self.uploadTasks.forEach { uploadTask in currentCompletedUnitCount += (uploadTask.snapshot.progress?.completedUnitCount ?? 0) currentTotalUnitCount += (uploadTask.snapshot.progress?.totalUnitCount ?? 0) } self.completedUnitCount = currentCompletedUnitCount self.totalUnitCount = currentTotalUnitCount self.progressHandler(currentCompletedUnitCount, currentTotalUnitCount) } uploadTask.observe(.success) { snapshot in if self.completedUnitCount == self.totalUnitCount { let metaDatas = self.uploadTasks.map { uploadTask -> StorageMetadata in return uploadTask.snapshot.metadata ?? StorageMetadata() } self.completeHandler(metaDatas) } } uploadTask.observe(.failure) { snapshot in if !self.isfailure { self.isfailure = true self.uploadTasks.forEach { uploadTask in if uploadTask.snapshot.progress?.isCancellable ?? false { uploadTask.cancel() } } let error = snapshot.error as NSError? self.failureHandler(error) } } } }エラーハンドリング
エラーから以下の情報がわかる。
sample.swiftswitch (StorageErrorCode(rawValue: error.code)!) { case .unknown: // An unknown error occurred break case .objectNotFound: // No object exists at the desired reference break case .bucketNotFound: // No bucket is configured for Firebase Storage break case .projectNotFound: // No project is configured for Firebase Storage break case .quotaExceeded : // Quota on your Firebase Storage bucket has been exceeded // If you're on the free tier, upgrade to a paid plan // If you're on a paid plan, reach out to Firebase support break case .unauthenticated: // User is unauthenticated. Authenticate and try again break case .unauthorized: // User is not authorized to perform the desired action // Check your rules to ensure they are correct break case .retryLimitExceeded : // The maximum time limit on an operation (upload, download, delete, etc.) has been exceeded // Try uploading again break case .nonMatchingChecksum : // File on the client does not match the checksum of the file received by the server // Try uploading again break case .downloadSizeExceeded : // Size of the downloaded file exceeds the amount of memory allocated for the download // Increase memory cap and try downloading again break case .cancelled: // User cancelled the operation break case .invalidArgument : // An invalid argument was provided break /* ... */ default: // A separate error occurred. This is a good place to retry the upload. break }
- 投稿日:2020-04-03T21:46:10+09:00
CoreDataを使ったTableViewでスワイプ削除を行うには
■はじめに
IntやStringの配列をTableViewに表示していてスワイプ削除を行いたい場合はこちらをお勧めします=>(スワイプでtableviewのセル削除)
CoreDataとTableViewはセットで使われることが多いです。
CoreDataを扱っている記事のほとんどがTableViewで取得したデータを表示しているためその辺でつまづいてもいくつか記事をあさって行けば問題ありません。
しかし、TableViewのCellをスワイプで削除する方法についての記事は少なく、どの記事も古い。しかもCoreDataとセットで扱ってる記事がないので、雑なコードだけでも載せておきますので、参考にしてみてください。
■動作環境
- Xcode Version 11.3.1
- Swift5
■ソースコード
ViewController.swift//〜〜 //割愛 //〜〜 // セルの削除許可を設定 func tableView(_ tableView: UITableView,canEditRowAt indexPath: IndexPath) -> Bool{ return true } // スワイプ削除 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { do { let fetchRequest = NSFetchRequest<NSManagedObject>(entityName:"FoodInformation") // 条件指定 ↓条件(Int型は%d)↓ ↓値↓ fetchRequest.predicate = NSPredicate(format: "foodId = %d", CoreDataArray[indexPath.row].foodId) let data = try managedObjectContext.fetch(fetchRequest) // 指定した条件の内容を削除 for task in data { managedObjectContext.delete(task) } // 削除したDBの変更内容を保存 (UIApplication.shared.delegate as! AppDelegate).saveContext() // CoreDataArrayはこのクラスの変数。DBよりあとに削除 self.CoreDataArray.remove(at: indexPath.row) //(※1) } catch { print("Fetching Failed.") } // TableViewのCellを削除 tableView.deleteRows(at: [indexPath], with: .automatic) } //〜〜 //割愛 //〜〜僕がつまづいたのは(※1)のCoreDataを保存した配列の削除の部分です。他の記事でこの部分を先に書くというものがありました。しかしそれはCoreDataを使うことを考慮していないものだったのでかなり惑わされてしまいました(笑)
このソースコードを、DelegateとかDataSourceとかセルのタップアクションとかみたいにはっつければ動きます。
entityNameや条件などは自身のソースコードに合うように変更してください。
- 投稿日:2020-04-03T21:46:10+09:00
【Swift5】CoreDataを使ったTableViewでスワイプ削除を行うには
■はじめに
IntやStringの配列をTableViewに表示していてスワイプ削除を行いたい場合はこちらをお勧めします=>(スワイプでtableviewのセル削除)
CoreDataとTableViewはセットで使われることが多いです。
CoreDataを扱っている記事のほとんどがTableViewで取得したデータを表示しているためその辺でつまづいてもいくつか記事をあさって行けば問題ありません。
しかし、TableViewのCellをスワイプで削除する方法についての記事は少なく、どの記事も古い。しかもCoreDataとセットで扱ってる記事がないので、雑なコードだけでも載せておきますので、参考にしてみてください。
■動作環境
- Xcode Version 11.3.1
- Swift5
■ソースコード
ViewController.swift//〜〜 //割愛 //〜〜 // セルの削除許可を設定 func tableView(_ tableView: UITableView,canEditRowAt indexPath: IndexPath) -> Bool{ return true } // スワイプ削除 func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { do { let fetchRequest = NSFetchRequest<NSManagedObject>(entityName:"FoodInformation") // 条件指定 ↓条件(Int型は%d)↓ ↓値↓ fetchRequest.predicate = NSPredicate(format: "foodId = %d", CoreDataArray[indexPath.row].foodId) let data = try managedObjectContext.fetch(fetchRequest) // 指定した条件の内容を削除 for task in data { managedObjectContext.delete(task) } // 削除したDBの変更内容を保存 (UIApplication.shared.delegate as! AppDelegate).saveContext() // CoreDataArrayはこのクラスの変数。DBよりあとに削除 self.CoreDataArray.remove(at: indexPath.row) //(※1) } catch { print("Fetching Failed.") } // TableViewのCellを削除 tableView.deleteRows(at: [indexPath], with: .automatic) } //〜〜 //割愛 //〜〜僕がつまづいたのは(※1)のCoreDataを保存した配列の削除の部分です。他の記事でこの部分を先に書くというものがありました。しかしそれはCoreDataを使うことを考慮していないものだったのでかなり惑わされてしまいました(笑)
このソースコードを、DelegateとかDataSourceとかセルのタップアクションとかみたいにはっつければ動きます。
entityNameや条件などは自身のソースコードに合うように変更してください。
- 投稿日:2020-04-03T19:33:05+09:00
今さらまとめるObjective-C文法基礎
案件でObjective-Cに携わることになり、今さらながらObjective-Cの構文を学習したので備忘録としてまとめました。
※ SwiftでiOSアプリを作ったことがある人向けです。変数・定数・型
宣言構文
// (1) 型を宣言をして代入する int age age = 23; // (2) 型宣言と同時に代入も可能 int age = 23; // (3) 同じデータ型の場合は複数同時に宣言可能 int width = 100, height = 88, area;型
基本データ型
データ型 説明 short int 16ビット符号付き整数 int 32ビット符号付き整数 long int 64ビット符号付き整数 float 32ビット符号付き少数。代入時数値の末尾にfが必要 double 64ビット符号付き少数 BOOL 真偽値。YES または NO で表現する char 半角英数字1文字のみ代入可能。 代入する文字はシングルクォーテーションで囲む ※
short int
,int
,long int
型のビット数はビルド環境によって変化するらしい。
※ Bool型はtrue
/false
でも真偽値になる。また、0
がNO
でそれ以外の数値
はYES
になる。ポインタ型(参照型)
基本データ型以外は全てポインタ型になる。
// ポインタ型変数として宣言する変数名の前に * (アスタリスク)をつける NSString *name = @"太郎";id型
どんな型でも代入可能な汎用的な型(Javaで言う
Object型
)。
参照型の1つだが、宣言時に*
が不要。定数
const
変数宣言時に
const
をつけることで定数を定義可能。
通常、ヘッダファイルまたはメインファイルの@interfece
文で宣言する。// const はデータ型の前後どちらにつけても良い const int LEVEL = 27; NSString * const COLOR = @"green";#define(マクロ定義)
#define
で指定された文字はコンパイル前に指定した値で書き換えられる。
#undef
を使うと、それ以降は文字の書き換えが行われなくなる。// ; は不要 #define LIMIT 100 #define URL @"hoge.com" // マクロ関数も定義可能 # define test(a, b) a + b NSLog(@"%d", test(1, 2)); // => 3enum
連続する整数を定義可能。
enum { NUM_A = 10, NUM_B, // => 11 NUM_C, // => 12 NUM_D, // => 13 NUM_E = 99, NUM_F, // => 100 }typedefを使ってenumをデータ型として定義する。
typedef enum { SUNNY, // => 0 CLOUDY, // => 1 RAINY, // => 2 } Weather;
NS_ENUM
を使う(この書き方が主流)typedef NS_ENUM(NSInteger, Weather) { WeatherSUNNY, // => 0 WeatherCLOUDY, // => 1 WeatherRAINY, // => 2 };
NS_ENUM
の利点
- switch文で記述漏れがないかコンパイラがチェックしてくれる
- 型を明示する必要があるため、より型安全
文字列 - NSString
// 宣言 + 代入 NSString *name = @"太郎"; // 比較 if ([@"hoge" isEqualToString:@"huga"]) { //=> false } // 連結 NSString *userName = [name stringByAppendingString:@" 様"]; // 文字数カウント NSInteger length = [name length]; // これでもOK プロパティはないがコンパイラが引数なしのメソッドと判断する NSInteger length = name.length // 数値 → 文字列 NSString *age = [NSString stringWithFormat:@"%d", num]; // 文字列 → 数値 NSInteger *num = [@"1000" integerValue];※ 文字列に変更を加えたい場合は
NSMutableString
クラスを使う配列 - NSArray, NSMutableArray
NSArray
: 要素の個数が固定NSMutableArray
: 要素の個数が可変// 宣言 + 代入 NSArray *alphabet = @[@"A", @"B", @"C"]; // 配列の要素はオブジェクトのみ 数値型やBOOL型を入れたい場合はNSNumberクラスとして入れる int num = 10; NSArray *numbers = @[@23, @190, @4, @(num)]; // 参照 NSString *b = alphabet[1]; // 末尾要素の参照(配列が空の場合はnil) NSString *lastAlphabet = [alphabet lastObject]; // 要素数の取得(count はプロパティではなくメソッドだが、この書き方が可能) int count = alphabet.count // -- 以下、NSMutableArrayのみ // 要素の追加 // 空の配列はNSMutableArrayクラスの親クラスのNSArrayクラスの array メソッドで作成可能 NSMutableArray *array [NSMutableArray array]; [array addObject:@"hoge"]; // 要素の挿入 NSMutableArray *greets [NSMutableArray arrayWithArray:@[@"こんにちは", @"Hello", @"Moi"]]; // atIndex番目に挿入する(先頭: 0) [greets insertObject:@"你好" atIndex:1]; // => (こんにちは, 你好, Hello, Moi) // 要素の削除 [greets removeObjectAtIndex:1]; // => (こんにちは, Hello, Moi) [greets removeObject:@"こんにちは"]; // => (Hello, Moi)辞書 - NSDictionary, NSMutableDictionary
// 宣言 @{キー:値, ...} の形式で書く キーはNSString, 値はオブジェクトのみ NSDictionary *wether = @{@"sunny":@"晴れ", @"cloudy":@"曇り", @"rainy":@"雨"}; // 参照 NSLog(@"%@", wether[@"sunny"]); // => 晴れ // -- 以下、NSMutableDictionaryのみ // 要素の追加 NSMutableDictionary *hoge = [NSMutableDictionary dictionaryWithCapacity:3]; hoge[@"A"] = @1; hoge[@"B"] = @2; // 値の変更(キーがない場合は追加になる) hoge[@"B"] = @2222; // 要素の削除(キーがない場合は何も起きない) [hoge removeObjectForKey:@"A"]; [hoge removeAllObjects];構造体 - Struct
// メンバにARCで管理されているオブジェクトを持つことはできない // 定義(typedefを使って型として定義されることが多い) struct _screen { unsigned int width; unsigned int height; } Screen // 宣言 + 代入 Screen iPhone; iPhone.width = 5; iPhone.height = 24;制御構文
条件分岐 - if
条件式の結果が
0
,NO
,NULL,
nil`以外の時、if文の中を実行する。// 普通にif文が書ける ※else ifもモチロン書ける int age = 35; if (age >= 20) { NSLog(@"You can drink."); } else { NSLog(@"You cannot drink."); } // => You can drink.条件分岐 - switch
case文の値は
数値
,char
,定数
のみ設定可能。int rank = 'B'; switch(score) { case 'A': NSLog(@"Your rank is A."); break; case 'B': NSLog(@"Your rank is B."); break; case 'C': NSLog(@"Your rank is C."); break; default: NSLog(@"You are not ranker..."); break; } // => Your rank is B.繰り返し - for
// カウンタを使う for (int i = 0; i < 10; i++) { NSLog(@"%d", i); } // コレクションクラスのfor inを使う NSArray *array = [@1, @2, @3]; NSInteger sum = 0; for (NSNumber *num in array) { sum += [num intValue]; } NSLog(@"%d", sum); // NSArrayでenumerateObjectsUsingBlockを使う(推奨) NSArray *array = [@1, @2, @3]; __block NSInteger sum = 0; [array enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { sum += obj; }]; NSLog(@"%d", sum);NSArrayの
enumerateObjectsUsingBlock
メソッドについては、こちらの記事の解説が分かりやすいです。
NSArrayでfor(; ;)とかfor-inを使うのをやめて、enumerateObjectsUsingBlock:を使う繰り返し - while
条件式が
YES
の間繰り返す。while (YES) { // 無限ループ }ログ出力 - NSLog
// NSLog(文字列オブジェクト); NSString *name = @"太郎"; NSLog(str); // => 太郎 // NSLog(フォーマット指定子を含んだ文字列オブジェクト, 値1, 値2, ...); NSLog(@"こんにちは、%s", name); // => こんにちは、太郎 // BOOL型の値は %d でフォーマットし、YES=1, NO=0を出力する BOOL isOK = YES; NSLog(@"Are you OK? %d", isOS); // => Are you OK? 1クラス構文
クラス定義はヘッダファイル
hoge.h
とメインファイル(実装ファイル)hoge.m
に分かれて定義される。ヘッダファイルの構造
hoge.h#import <フレームワーク/ファイル> #import ソースファイル // @interface クラス名 : 継承元クラス名 <プロトコル> interface Hoge : Huga <MyProtcol> { // インスタンス変数宣言 // データ型 変数名; NSInteger num1, num2; NSArray *array; } // プロパティ変数宣言 // @property (属性) データ型 プロパティ名; @property (assign, readonly, noatomic) NSInteger * num; // assign: 参照ではなく値を割り当てる // strong: ARCでカウントする参照(初期値) // weak : ARCでカウントしない参照 // copy : オブジェクトをコピーする // -- // readonly : 読み出しのみ // readwrite: 読み書き可能(初期値) // -- // noatomic : アトミック(別スレッドから同時に参照された際に同値を保証する状態)ではない(初期状態はアトミック) // getter=ゲッタ名: 独自のゲッタメソッドを指定(noatomicを指定した場合のみ) // setter=セッタ名: 独自のセッタメソッドを指定(noatomicを指定した場合のみ) // メソッド宣言 // + (戻り値の型) メソッド名:(引数1の型)引数1 ラベル2:(引数2の型)引数2 ...; // - (戻り値の型) メソッド名:(引数1の型)引数1 ラベル2:(引数2の型)引数2 ...; // + はクラスメソッド // - はインスタンスメソッド - (NSString *) fullName:(NSString *)firstName lastName:(NSString *)lastName; @endメインファイルの構造
hoge.m#import ヘッダファイル // @interface クラス名() <プロトコル> // @interface ~ @end はクラスエクステンションと呼ぶ // クラス内のみで使えるインスタンス変数、プリパティ変数、メソッドを宣言できる(子クラスからも使えない) @interface Hoge() <MyProtcol> { // インスタンス変数宣言 } // プロパティ変数宣言 // メソッド宣言 @end // @implementation クラス名 @implementation Hoge // @implementation ~ @end はインプリメンテーションセクションと呼ぶ // ヘッダファイル, クラスエクステンションで宣言したプロパティの初期化やメソッドの定義を行う - (NSString *) fullName:(NSString *)firstName lastName:(NSString *)lastName { return [NSString stringWithFormat:@"%@ %@", firstName, lastName]; } - (void) countup { // 自身のプロパティにアクセスする際は _ をつける か self をつける _count++; // self.count++; でもOK } @endクラスの使い方
// インスタンスの作成/初期化 alloc でメモリ確保 init で初期化 NSDate *theDate = [[NSDate alloc] init]; // コンビニエンスコンストラクタ // 例えば、NSDateクラスでは、allocし現在時刻でinitしたNSDate型を返すdateクラスメソッドが用意されている NSDate *today = [NSDate date]; // プロパティへのアクセス int huga = myObj.hoge; // メソッドの実行(メッセージ式) [クラス名/インスタンス名 メソッド名:引数]; [hogeObj hogeFunc:100]; // 複数引数の場合はラベルをつける 引数1のラベルがメソッド名なことに注意 [hogeObj hogeFunc:10 a:82 b:@"b"];参考文献
- 大重美幸, 詳細!Objective-C iPhoneアプリ開発入門ノート Xcode5+iOS7対応, 株式会社 ソーテック社, 2013.
- 投稿日:2020-04-03T19:13:12+09:00
iOSアプリのAmazonリンクからAmazonアプリを起動する方法
Info.plistにLSApplicationQueriesSchemesというキーでTypeをArrayにし、そこにアイテムを追加し、TypeをStringでvalueをcom.amazon.mobile.shopping.webにします
アソシエイトリンクの場合は、短縮URL
https://amzn.to/xxx
を使うとAmazon モバイルは現在amzn.toでご利用いただけません
というエラーがでるので通常URLを使う。urlString = "アソシエイトリンクの通常URLからhttps://を除いた文字列" let urlStringWithSchema = "com.amazon.mobile.shopping.web://" + urlString guard let urlWithSchema = URL(string: urlStringWithSchema) else { return } if UIApplication.shared.canOpenURL(urlWithSchema){ UIApplication.shared.open(urlWithSchema, options: [:], completionHandler: nil) } else { guard let url = URL(string: urlString) else { return } UIApplication.shared.open(url, options: [:], completionHandler: nil) }一度アプリの該当画面からAmazonアプリを開く許可をすると、以後は許可を求められずに勝手にAmazonアプリが開く。これは Cunstom URL Schemas
Apple Developer のリファレンス等にあるような、
URL Types
に値を追加する云々は、あくまでSafariなどでブラウジングしていて特定のURLを踏んだときに自社アプリを起動させるための設定なので混同しないように。参考文献
https://lima.world/iphone-url/
https://qiita.com/zakiyamaaaaa/items/96c6fb8ee5a93c963bbc
https://stackoverflow.com/questions/27693062/iphone-ios-amazon-scheme-uri-not-working-anymore-amzn-version-4-3-1-deep-li
※amzn://content/item?id=<some valid id>
という方式は使えなくなったようだ。
- 投稿日:2020-04-03T17:27:04+09:00
[swift]textViewにplaceHolder的なのをつけてみる
はじめに
こんにちは。swiftはじめて約1年のMomijiです!
今回ははじめての記事となるのですが、textViewにplaceHolder的なのをつけてみるということで書かせていただきます。
⚠️かなり自己流です。
textFieldにはあって、なんでtextViewにはplaceHolderないんでしょうね〜コード
とりあえず、全体のコードです。
import UIKit class SSubjectsDetailViewController: UIViewController,UITextViewDelegate { @IBOutlet weak var textView: UITextView! override func viewDidLoad() { textView.text = "内容を入力" textView.textColor = .lightGray textView.delegate = self } func textViewDidBeginEditing(_ textView: UITextView) { textView.text = "" textView.textColor = .black } func textViewDidEndEditing(_ textView: UITextView) { if textView.text == "" { textView.text = "内容を入力" textView.textColor = .lightGray } } }解説
textView.text = "内容を入力" textView.textColor = .lightGray内容を入力のところは、ご自分でpalceHolderに設定したい文字を入れてください。
placeHolderぽくするためにtextViewの文字色をlightgrayにしました。func textViewDidBeginEditing(_ textView: UITextView) { textView.text = "" textView.textColor = .black }textViewDidBeginEditingで、textViewがタップされた時の挙動を表します。
つまり、「textViewがタップされた時、textViewの文字を無くして、色を黒にする」
という処理を行なっています。func textViewDidBeginEditing(_ textView: UITextView) { if textView.text == "" { textView.text = "内容を入力" textView.textColor = .lightGray } }ここは、placeHolderを実装するだけならいらない部分なのかもしれないのですが、
textViewに何も文字が書いていない場合のtextViewの処理を表しています。まとめ
う〜ん、やっぱりtextViewにもplaceHolderは欲しいですね〜
冒頭にも書きましたが、かなり自己流ですが、一応placeHolder的なものはできました。
たくさんの方の役に立つと嬉しいです。
質問がある方は、コメント欄まで〜
- 投稿日:2020-04-03T12:07:44+09:00
【Swift】Modalで表示した子画面の閉じるイベントを親画面で検知する
概要
iOS13からModalで画面遷移した際のアニメーションが変更されました。
それによって、Modal画面を閉じた際に動くライフサイクルメソッドが変わりました。
(今まではViewWillAppearが動いていたので、そこで更新等の処理を書いていた人も多いはず。困った、、、)Modalで表示した子画面の閉じるイベントを親画面で検知する方法を備忘として残しておきます。
環境
Xcode: 11.4
Swift: Swift5手順1
適当なViewControllerを2つ用意。
ここでは遷移元を「ParentViewController」遷移先を「ChildViewController」とします。ParentViewController.swiftimport UIKit final class ParentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } }ChildViewController.swiftimport UIKit final class ChildViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } }手順2
ChildViewControllerが閉じた際のイベントは以下のメソッドで検知可能です。
- (void)presentationControllerDidDismiss:(UIPresentationController *)presentationController「UIAdaptivePresentationControllerDelegate」に準拠することで使用可能です。
ParentViewControllerに以下を実装します。ParentViewController.swiftimport UIKit final class ParentViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let storyboard = UIStoryboard.init(name: "Child", bundle: nil) let vc = storyboard.instantiateViewController(withIdentifier: "AccountViewController") // delegateを設定 vc.presentationController?.delegate = self self.present(vc, animated: true, completion: nil) } } extension ParentViewController: UIAdaptivePresentationControllerDelegate { func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { // ChildViewControllerのDismissを検知 print("closed") } }最後に
iOS13が出てから半年以上経っているのに調べるまで知りませんでした。
ライフサイクルメソッドに書かなくていいので実装がスッキリしますね!
誰かのお役に立てれば幸いです!
- 投稿日:2020-04-03T11:29:06+09:00
画像ファイルの種類を判別するDataのExtension
Swiftでファイルを取扱う際、
URL
であればpathExtension
で拡張子を取得することができます。しかしData
からファイルの種類を特定する場合、基本機能だけでは不可能です。NSDataから判別する記事を書いている方がいたので、これを元にData
から画像の種別を特定するExtensionを作成しました。コード全貌 (+解説)
extension Data { enum ImageType: String, CaseIterable { case jpg case png case gif case bmp var header: UInt8 { switch self { case .jpg: return 0xff case .png: return 0x89 case .gif: return 0x47 case .bmp: return 0x42 } } var `extension`: String { return "." + rawValue } } var imageType: ImageType? { var values = [UInt8](repeating: 0, count: 1) copyBytes(to: &values, count: 1) return ImageType.allCases.first { $0.header == values[0] } } }画像ファイルの種別はバイナリデータのヘッダを見ればわかる(らしい)ので、その情報を持つ
enum ImageType
を定義しておき、var imageType
内で実際のヘッダとの比較を行っています。判別する画像の種類を増やしたいときはenum ImageType
のcase
を追加すればよいです。楽ですね。まとめ
正直バイナリとか触ったことなくて不安しかないので、間違い等あればご指摘お願いします...
参考文献