Last active
October 5, 2017 07:07
-
-
Save fumiyasac/f991cb3ff0e4257fa8ea3cad67bbdf73 to your computer and use it in GitHub Desktop.
ジェスチャーやカスタムトランジションを利用して入力時やコンテンツ表示時に一工夫を加えたUIの実装ポイントまとめ ref: http://qiita.com/fumiyasac@github/items/6c4c2b909a821932be04
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//レシピを登録する時のアクション | |
@IBAction func saveRecipeAction(_ sender: UIButton) { | |
//キーボードを閉じる | |
view.endEditing(true) | |
//ボタンを非活性状態にする | |
closeButton.isEnabled = false | |
saveButton.isEnabled = false | |
//Realmにデータを保存してポップアップを閉じる | |
saveArchiveData() | |
removeAnimatePopup() | |
} | |
//Realmへの保存処理を行う(Archive:1件・Recipe:n件) | |
fileprivate func saveArchiveData() { | |
//Realmへの登録処理 | |
let archiveObject = Archive.create() | |
let archive_id = Archive.getLastId() | |
archiveObject.memo = memoTextField.text! | |
archiveObject.created = DateConverter.convertStringToDate(dateTextField.text) | |
archiveObject.save() | |
for targetData in targetSelectedDataList { | |
let recipeObject = Recipe.create() | |
recipeObject.archive_id = archive_id | |
recipeObject.rakuten_id = targetData.id | |
recipeObject.rakuten_indication = targetData.indication | |
recipeObject.rakuten_published = targetData.published | |
recipeObject.rakuten_title = targetData.title | |
recipeObject.rakuten_image = targetData.image | |
recipeObject.rakuten_url = targetData.url | |
recipeObject.save() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import RealmSwift | |
class Archive: Object { | |
//Realmクラスのインスタンス | |
static let realm = try! Realm() | |
//id | |
dynamic var id = 0 | |
//メモ | |
dynamic var memo = "" | |
//登録日 | |
dynamic var created = Date(timeIntervalSince1970: 0) | |
//PrimaryKeyの設定 | |
override static func primaryKey() -> String? { | |
return "id" | |
} | |
//プライマリキーの作成メソッド | |
static func getLastId() -> Int { | |
if let archive = realm.objects(Archive.self).last { | |
return archive.id + 1 | |
} else { | |
return 1 | |
} | |
} | |
//新規追加用のインスタンス生成メソッド | |
static func create() -> Archive { | |
let archive = Archive() | |
archive.id = self.getLastId() | |
return archive | |
} | |
//インスタンス保存用メソッド | |
func save() { | |
try! Archive.realm.write { | |
Archive.realm.add(self) | |
} | |
} | |
//インスタンス削除用メソッド | |
func delete() { | |
try! Archive.realm.write { | |
Archive.realm.delete(self) | |
} | |
} | |
//登録日順のデータの全件取得をする | |
static func fetchAllCalorieListSortByDate() -> [Archive] { | |
let archives = realm.objects(Archive.self).sorted(byProperty: "created", ascending: false) | |
var archiveList: [Archive] = [] | |
for archive in archives { | |
archiveList.append(archive) | |
} | |
return archiveList | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ArchiveCell: UITableViewCell { | |
//ArchiveRecipeController.swiftへ処理内容を引き渡すためのクロージャーを設定 | |
var showGalleryClosure: (() -> ())? | |
var deleteArchiveClosure: (() -> ())? | |
・・・(省略)・・・ | |
/* (Button Actions) */ | |
//「レシピ一覧へ」ボタン押下時のアクション | |
@IBAction func showRecipeGalleryAction(_ sender: UIButton) { | |
showGalleryClosure!() | |
} | |
//「削除する」ボタン押下時のアクション | |
@IBAction func deleteRecipeAction(_ sender: UIButton) { | |
deleteArchiveClosure!() | |
} | |
・・・(省略)・・・ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//表示するセルの中身を設定する | |
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
//Xibファイルを元にデータを作成する | |
let cell = tableView.dequeueReusableCell(withIdentifier: "ArchiveCell") as? ArchiveCell | |
//アーカイブデータが空でなければセルにレシピデータを表示する | |
if !archiveList.isEmpty { | |
//該当のArchiveデータとそれに紐づくRecipeデータを取得 | |
let archiveData = archiveList[indexPath.row] | |
let recipes: [Recipe] = Recipe.fetchAllRecipeListByArchiveId(archive_id: archiveData.id) | |
//1枚目の画像を見せておくようにする | |
let url = URL(string: (recipes.first?.rakuten_image)!) | |
cell?.archiveImageView.kf.indicatorType = .activity | |
cell?.archiveImageView.kf.setImage(with: url) | |
//レシピギャラリー一覧ページを表示する | |
cell?.showGalleryClosure = { | |
//遷移元からポップアップ用のGalleryControllerのインスタンスを作成する | |
let galleryVC = UIStoryboard(name: "Gallery", bundle: nil).instantiateViewController(withIdentifier: "GalleryController") as! GalleryController | |
//ポップアップ用のViewConrollerを設定し、modalPresentationStyle(= .overCurrentContext)と背景色(= UIColor.clear)を設定する | |
galleryVC.modalPresentationStyle = .overCurrentContext | |
galleryVC.view.backgroundColor = UIColor.clear | |
//変数の受け渡しを行う | |
galleryVC.recipeData = recipes | |
//ポップアップ用のViewControllerへ遷移 | |
self.present(galleryVC, animated: false, completion: nil) | |
} | |
//データ表示用のUIAlertControllerを表示する | |
cell?.deleteArchiveClosure = { | |
//データ削除の確認用ポップアップを表示する | |
let deleteAlert = UIAlertController( | |
title: "データ削除", | |
message: "このデータを削除しますか?(削除をする場合にはこのデータに紐づくレシピデータも一緒に削除されます。)", | |
preferredStyle: UIAlertControllerStyle.alert | |
) | |
deleteAlert.addAction( | |
UIAlertAction( | |
title: "OK", | |
style: UIAlertActionStyle.default, | |
handler: { (action: UIAlertAction!) in | |
//Realmから該当データを1件削除する処理 | |
for recipe in recipes { | |
recipe.delete() | |
} | |
archiveData.delete() | |
//登録されているデータの再セットを行う | |
self.archiveList = Archive.fetchAllCalorieListSortByDate() | |
}) | |
) | |
deleteAlert.addAction( | |
UIAlertAction( | |
title: "キャンセル", | |
style: UIAlertActionStyle.cancel, | |
handler: nil | |
) | |
) | |
self.present(deleteAlert, animated: true, completion: nil) | |
} | |
//アーカイブデータを取得する | |
cell?.archiveDate.text = DateConverter.convertDateToString(archiveData.created) | |
cell?.archiveMemo.text = archiveData.memo | |
} | |
cell?.accessoryType = UITableViewCellAccessoryType.none | |
cell?.selectionStyle = UITableViewCellSelectionStyle.none | |
return cell! | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
//カレンダー配置用ボタンを作成する構造体 | |
struct CalendarView { | |
//現在日付のカレンダー一覧を取得する | |
static func getCalendarOfCurrentButtonList() -> [UIButton] { | |
//年・月・最後の日を取得 | |
let values: (year: Int, month: Int, max: Int) = getCalendarOfCurrentValues() | |
let year = values.year | |
let month = values.month | |
let max = values.max | |
//ボタンの一覧を入れるための配列 | |
var buttonArray: [UIButton] = [] | |
//祝祭日判定用のインスタンス | |
let holiday = CalculateCalendarLogic() | |
for i in 1...max { | |
//カレンダー選択用ボタンを作成する | |
let button: UIButton = UIButton() | |
//祝祭日の判定を行う | |
let holidayFlag = holiday.judgeJapaneseHoliday(year: year, month: month, day: i) | |
//曜日の数値を取得する(0:日曜日 ... 6:土曜日) | |
let weekday = Weekday.init(year: year, month: month, day: i) | |
let weekdayValue = weekday?.rawValue | |
let weekdayString = weekday?.englishName | |
//タグと日付の設定を行う | |
button.setTitle(weekdayString! + "\n" + String(i), for: UIControlState()) | |
button.titleLabel!.font = UIFont(name: "Arial", size: 12)! | |
button.titleLabel!.numberOfLines = 2 | |
button.titleLabel!.textAlignment = .center | |
button.tag = i | |
//日曜日or祝祭日の場合の色設定 | |
if weekdayValue! % 7 == 0 || holidayFlag == true { | |
button.backgroundColor = UIColor(red: CGFloat(0.831), green: CGFloat(0.349), blue: CGFloat(0.224), alpha: CGFloat(1.0)) | |
//土曜日の場合の色設定 | |
} else if weekdayValue! % 7 == 6 { | |
button.backgroundColor = UIColor(red: CGFloat(0.400), green: CGFloat(0.471), blue: CGFloat(0.980), alpha: CGFloat(1.0)) | |
//平日の場合の色設定 | |
} else { | |
button.backgroundColor = UIColor.lightGray | |
} | |
//設定したボタンの一覧を配列に入れる | |
buttonArray.append(button) | |
} | |
return buttonArray | |
} | |
・・・(省略)・・・ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
class CustomTransition: NSObject, UIViewControllerAnimatedTransitioning { | |
//トランジションの秒数 | |
let duration = 0.18 | |
//トランジションの方向(present: true, dismiss: false) | |
var presenting = true | |
//アニメーション対象なるサムネイル画像の位置やサイズ情報を格納するメンバ変数 | |
var originalFrame = CGRect.zero | |
//アニメーションの時間を定義する | |
internal func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { | |
return duration | |
} | |
/** | |
* アニメーションの実装を定義する | |
* この場合には画面遷移コンテキスト(UIViewControllerContextTransitioningを採用したオブジェクト) | |
* → 遷移元や遷移先のViewControllerやそのほか関連する情報が格納されているもの | |
*/ | |
internal func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { | |
//コンテキストを元にViewのインスタンスを取得する(存在しない場合は処理を終了) | |
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else { | |
return | |
} | |
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { | |
return | |
} | |
//アニメーションの実態となるコンテナビューを作成 | |
let containerView = transitionContext.containerView | |
//遷移先のViewController・始めと終わりのViewのサイズ・拡大値と縮小値を決定する | |
var targetView: UIView! | |
var initialFrame: CGRect! | |
var finalFrame: CGRect! | |
var xScaleFactor: CGFloat! | |
var yScaleFactor: CGFloat! | |
//Case1: 進む場合 | |
if presenting { | |
targetView = toView | |
initialFrame = originalFrame | |
finalFrame = targetView.frame | |
xScaleFactor = initialFrame.width / finalFrame.width | |
yScaleFactor = initialFrame.height / finalFrame.height | |
//Case2: 戻る場合 | |
} else { | |
targetView = fromView | |
initialFrame = targetView.frame | |
finalFrame = originalFrame | |
xScaleFactor = finalFrame.width / initialFrame.width | |
yScaleFactor = finalFrame.height / initialFrame.height | |
} | |
//アファイン変換の倍率を設定する | |
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor) | |
//進む場合の遷移時には画面いっぱいに画像を表示させるようにするための位置補正を行う | |
if presenting { | |
targetView.transform = scaleTransform | |
targetView.center = CGPoint(x: initialFrame.midX, y: initialFrame.midY) | |
targetView.clipsToBounds = true | |
} | |
//アニメーションの実体となるContainerViewに必要なものを追加する | |
containerView.addSubview(toView) | |
containerView.bringSubview(toFront: targetView) | |
UIView.animate(withDuration: duration, delay: 0.0, options: .curveEaseOut, animations: { | |
//変数durationでの設定した秒数で拡大・縮小を行う | |
targetView.transform = self.presenting ? CGAffineTransform.identity : scaleTransform | |
targetView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY) | |
}, completion:{ finished in | |
transitionContext.completeTransition(true) | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
platform :ios, '9.0' | |
swift_version = '3.0' | |
target 'DraggableImageForm' do | |
use_frameworks! | |
pod 'RealmSwift' | |
pod 'Alamofire' | |
pod 'SwiftyJSON' | |
pod 'Kingfisher' | |
post_install do |installer| | |
installer.pods_project.targets.each do |target| | |
target.build_configurations.each do |config| | |
config.build_settings['SWIFT_VERSION'] = '3.0' | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import Kingfisher | |
class GalleryController: UIViewController, UIScrollViewDelegate, UIViewControllerTransitioningDelegate { | |
//タップ時に選択したimageViewを格納するための変数 | |
var selectedImage: UIImageView? | |
・・・(省略)・・・ | |
//レイアウト処理が完了した際のライフサイクル | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
・・・(省略)・・・ | |
//サムネイルにタグ名とTapGestureを付与する | |
thumbnailImageView.tag = i | |
thumbnailImageView.isUserInteractionEnabled = true | |
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(GalleryController.expandThumbnail(sender:))) | |
thumbnailImageView.addGestureRecognizer(tapGesture) | |
} | |
} | |
//サムネイルを拡大表示するためのアクション | |
func expandThumbnail(sender: UITapGestureRecognizer) { | |
//遷移対象をサムネイル画像とデータを設定する | |
selectedImage = sender.view as? UIImageView | |
let tagNumber = sender.view?.tag | |
let selectedRecipeData = recipeData[tagNumber!] | |
//カスタムトランジションを適用した画面遷移を行う | |
let garellyDetail = storyboard!.instantiateViewController(withIdentifier: "GalleryDetailController") as! GalleryDetailController | |
garellyDetail.recipe = selectedRecipeData | |
garellyDetail.transitioningDelegate = self | |
self.present(garellyDetail, animated: true, completion: nil) | |
} | |
/* (UIViewControllerTransitioningDelegate) */ | |
/** | |
* カスタムトランジションは下記のサンプルをSwift3に置き換えて再実装 | |
* (実装の詳細はCustomTransition.swiftを参考) | |
* | |
* 参考:iOS Animation Tutorial: Custom View Controller Presentation Transitions | |
* https://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions | |
*/ | |
//進む場合のアニメーションの設定を行う | |
internal func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
transition.originalFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil) | |
transition.presenting = true | |
return transition | |
} | |
//戻る場合のアニメーションの設定を行う | |
internal func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { | |
transition.presenting = false | |
return transition | |
} | |
・・・(省略)・・・ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
import Kingfisher | |
import SafariServices | |
class GalleryDetailController: UIViewController, UIViewControllerTransitioningDelegate, SFSafariViewControllerDelegate { | |
・・・(省略)・・・ | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
・・・(省略)・・・ | |
//背景のimageViewにタグ名とTapGestureを付与する | |
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(GalleryDetailController.backThumbnail(sender:))) | |
backgroundImageView.addGestureRecognizer(tapGesture) | |
・・・(省略)・・・ | |
} | |
//前の画面に戻るアクションをTapGestureをトリガーにして実行する | |
func backThumbnail(sender: UITapGestureRecognizer) { | |
presentingViewController?.dismiss(animated: true, completion: nil) | |
} | |
・・・(省略)・・・ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//メニューボタンを開く処理を実装するためのプロトコル | |
protocol MenuOpenDelegate { | |
func openMenuStatus(status: MenuStatus) | |
} | |
class MakeRecipeController: UIViewController, UINavigationControllerDelegate, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { | |
//メニュー部分開閉用のプロトコルのための変数 | |
var delegate: MenuOpenDelegate! = nil | |
・・・(省略)・・・ | |
//メニューボタンを押した時のアクション | |
func menuButtonTapped(button: UIButton) { | |
//デリゲートメソッドの実行(処理の内容はViewControllerに記載する) | |
self.delegate.openMenuStatus(status: MenuStatus.opened) | |
} | |
・・・(省略)・・・ | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import RealmSwift | |
class Recipe: Object { | |
//Realmクラスのインスタンス | |
static let realm = try! Realm() | |
//id | |
dynamic fileprivate var id = 0 | |
//archive_id | |
dynamic var archive_id = 0 | |
//楽天レシピid | |
dynamic var rakuten_id = "" | |
//楽天レシピ調理時間のめやす | |
dynamic var rakuten_indication = "" | |
//楽天レシピ公開日 | |
dynamic var rakuten_published = "" | |
//楽天レシピタイトル | |
dynamic var rakuten_title = "" | |
//楽天レシピ画像URL | |
dynamic var rakuten_image = "" | |
//楽天レシピURL | |
dynamic var rakuten_url = "" | |
//PrimaryKeyの設定 | |
override static func primaryKey() -> String? { | |
return "id" | |
} | |
//プライマリキーの作成メソッド | |
static func getLastId() -> Int { | |
if let recipe = realm.objects(Recipe.self).last { | |
return recipe.id + 1 | |
} else { | |
return 1 | |
} | |
} | |
//新規追加用のインスタンス生成メソッド | |
static func create() -> Recipe { | |
let recipe = Recipe() | |
recipe.id = self.getLastId() | |
return recipe | |
} | |
//インスタンス保存用メソッド | |
func save() { | |
try! Recipe.realm.write { | |
Recipe.realm.add(self) | |
} | |
} | |
//インスタンス削除用メソッド | |
func delete() { | |
try! Recipe.realm.write { | |
Recipe.realm.delete(self) | |
} | |
} | |
//アーカイブIDに紐づくデータを全件取得をする | |
static func fetchAllRecipeListByArchiveId(archive_id: Int) -> [Recipe] { | |
let recipes = realm.objects(Recipe.self).filter("archive_id = %@", archive_id).sorted(byProperty: "id", ascending: true) | |
var recipeList: [Recipe] = [] | |
for recipe in recipes { | |
recipeList.append(recipe) | |
} | |
return recipeList | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import UIKit | |
//メニュー部分の開閉状態管理用のenum | |
enum MenuStatus { | |
case opened | |
case closed | |
} | |
class ViewController: UIViewController, MenuOpenDelegate, MenuCloseDelegate { | |
//各種パーツのOutlet接続 | |
@IBOutlet weak var mainMenuContainer: UIView! | |
@IBOutlet weak var subMenuContainer: UIView! | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
・・・(省略)・・・ | |
} | |
override func viewDidLayoutSubviews() { | |
super.viewDidLayoutSubviews() | |
//矩形のままでアニメーションをさせるためにコードで再配置する | |
mainMenuContainer.frame = CGRect(x: 0, y: 0, width: mainMenuContainer.frame.width, height: mainMenuContainer.frame.height) | |
subMenuContainer.frame = CGRect(x: 0, y: 0, width: subMenuContainer.frame.width, height: subMenuContainer.frame.height) | |
} | |
/** | |
* 複雑な遷移時のプロトコルの適用 | |
* | |
* (Case1) | |
* UINavigationController → 任意のViewControllerとStoryBoardで設定した際に、 | |
* 任意のViewControllerに定義したプロトコルを適用させる場合 | |
* | |
* (Case2) | |
* ContainerViewで接続された任意のViewControllerに対して、 | |
* 任意のViewControllerに定義したプロトコルを適用させる場合 | |
* | |
* overrideしたprepareメソッドを利用する。 | |
* [Step1] それぞれの接続しているSegueに対してIdentifier名を定める | |
* [Step2] 下記のidentifierに関連するViewControllerのインスタンスを取得してデリゲートを適用する | |
* (UINavigationControllerの場合はちょっと注意) | |
* | |
* (参考): Containerとの値やり取り方法 | |
* http://qiita.com/BOPsemi/items/dd65b2b7cd83ec1e82b9 | |
*/ | |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { | |
if segue.identifier == "MakeRecipe" { | |
let navigationController = segue.destination as! UINavigationController | |
let makeRecipeController = navigationController.viewControllers.first as! MakeRecipeController | |
makeRecipeController.delegate = self | |
} | |
if segue.identifier == "Menu" { | |
let menuController = segue.destination as! MenuController | |
menuController.delegate = self | |
} | |
} | |
/* MenuOpenDelegate */ | |
func openMenuStatus(status: MenuStatus) { | |
changeMenuStatus(status) | |
} | |
/* MenuCloseDelegate */ | |
func closeMenuStatus(status: MenuStatus) { | |
changeMenuStatus(status) | |
} | |
//enumの値に応じてのステータス変更を行う | |
fileprivate func changeMenuStatus(_ targetStatus: MenuStatus) { | |
if targetStatus == MenuStatus.opened { | |
//メニューを表示状態にする | |
UIView.animate(withDuration: 0.16, delay: 0, options: .curveEaseOut, animations: { | |
self.mainMenuContainer.isUserInteractionEnabled = false | |
self.mainMenuContainer.frame = CGRect(x: 0, y: 320, width: self.mainMenuContainer.frame.width, height: self.mainMenuContainer.frame.height) | |
}, completion: nil) | |
} else { | |
//メニューを非表示状態にする | |
UIView.animate(withDuration: 0.16, delay: 0, options: .curveEaseOut, animations: { | |
self.mainMenuContainer.isUserInteractionEnabled = true | |
self.mainMenuContainer.frame = CGRect(x: 0, y: 0, width: self.mainMenuContainer.frame.width, height: self.mainMenuContainer.frame.height) | |
}, completion: nil) | |
} | |
} | |
・・・(省略)・・・ | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment