- 投稿日:2020-07-30T22:13:23+09:00
【Swift 】[String]()とは
var interestId = String
変数宣言でよくみるこの配列の後ろについた()
そいやあこいつ、なんで()がついてんの?
って思ったので、忘備録として記録しておきます。
Swiftでの配列の書式とコードの書き方 空で初期化する場合は下記の書き方をします。 var 配列変数名:[型名] = [] var 配列変数名 = [型名]()ほーなるほど。
var 配列変数名:[型名] = []
と書いても同じなんですね。
空で初期化する時にこの書き方をするらしいです。
- 投稿日:2020-07-30T22:05:32+09:00
[swift]MVCモデルに関して簡潔にまとめました。
MVCモデルとは
Model
,View
,Controller
の頭文字をとった略称でそれぞれのフォルダを作成してプログラムの構成を他者が見た場合でもわかりやすくする手法。この手法は多くのプログラマに使用されており、共通のストラクチャーとして認識してもらいやすい。
また、分業して作業する事ができる点や、変更や修正があった場合に影響を受けにくいなどというメリットが存在する。Model
機能に関する処理を行う。(データの処理を行う)
View
表示と、ユーザーからのアクションを受ける処理を行う。
Controller
機能とアクションを用いてViewに見せる処理を行う。
最後に
今回はMVCモデルに関してかなり簡潔にまとめました。
是非参考にしてください!
- 投稿日:2020-07-30T20:53:36+09:00
SwiftとFirebaseでSNSアプリNomadを開発しよう!(第0回)
どんなアプリを作るのか
「SNSアプリをSwiftで作ってみたいけどどのように作ればいいか分からない、、、」
「どのようにアプリをリリースさせればいいか分からない、、、」このようにお悩みの方は多いと思います。実際私もSwiftを学び始めた頃は自分が理想とするアプリなんて作れるはずがないと思っていました。(単純な画面遷移すら苦労していました笑)
しかし、自分なりにSwiftを学んでみて、この度「Nomad」というアプリを開発し、App Storeにリリースすることができました。Nomadは趣味で繋がれるコミュニティ型のサービスであり、共通の話題さえあればトークできる点が特徴です。
つまり、年齢も経歴も性別も思想も関係なく、誰でも平等な関係でつながれます。これは、昨今問題となっている新型コロナウイルスの流行から着想を得ました。多くの人々が外出自粛を余儀なくされ、閉鎖的な空間にいる現状のなか、「誰かとつながりたい」「ぬくもりを感じたい」と思う人々の欲求に寄り添ったサービスをつくろうと思い、サービスの開発に取り組みました。このアプリの作り方を共有して、少しでもiOSアプリを開発しようとしている皆さんの参考になれば嬉しいです!
なお、私自身もまだまだ未熟な点が多々あるので、「こういう風にしたらいいんじゃない?」といった指摘がございましたら、どんどんコメントをしていただけると嬉しいです!では、次回から早速開発をしていきます!
- 投稿日:2020-07-30T20:17:14+09:00
iOSアプリ初リリースからこれまでにApp Store Reviewチームからリジェクトされた理由5選
私の最初のアプリ(WalCal)をAppStoreにリリースしてから、約9ヶ月が経ちました。リリースから今まで計7回AppleReviewの審査リジェクトされました。今回はそのリジェクト内容を紹介します。
1. 画像が鮮明ではない
WalCal(ウォーカル)の決済画面に利用可能なクレジットカード会社(VISA, JCB, MasterCard)のロゴ(UIImageView)をStackviewを使い表示しているのですが、これが全体的に伸びてしまったいたためでデザイン面でApple Reviewチームからリジェクトを食らいました。
対処法はUIImageViewのContentModeをScaleAspectFillで設定していたのを、scaleAspectFitに変更しました。
以下がコードです。
func setupView() { //StripeSDK内のクレジットカード画像アセットを使用しています let visa = UIImageView(image: STPImageLibrary.visaCardImage()) let master = UIImageView(image: STPImageLibrary.masterCardCardImage()) let amex = UIImageView(image: STPImageLibrary.amexCardImage()) let discover = UIImageView(image: STPImageLibrary.discoverCardImage()) let jcb = UIImageView(image: STPImageLibrary.jcbCardImage()) let diners = UIImageView(image: STPImageLibrary.dinersClubCardImage()) let cardBrands = [visa, master, amex, discover, jcb, diners] //修正前のコード cardBrands.forEach{($0.contentMode = .scaleAspectFill)} //修正後のコード cardBrands.forEach{($0.contentMode = .scaleAspectFit)} let cardsStackView = UIStackView(arrangedSubviews: cardBrands) cardsStackView.axis = .horizontal cardsStackView.spacing = 1 cardsStackView.distribution = .fillEqually let cardContainerView = UIView() addSubview(dismissButton) dismissButton.anchor(top: topAnchor, left: leftAnchor, bottom: nil, right: nil, paddingTop: 10, paddingLeft: 10, paddingBottom: 0, paddingRight: 0, height: 0, width: 0) addSubview(cardContainerView) cardContainerView.anchor(top: dismissButton.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 10, paddingLeft: 30, paddingBottom: 0, paddingRight: 30, height: 50, width: 0) cardContainerView.addSubview(cardsStackView) cardsStackView.anchor(top: cardContainerView.topAnchor, left: cardContainerView.leftAnchor, bottom: cardContainerView.bottomAnchor, right: cardContainerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 0, width: 0) let separatorView1 = UIView() separatorView1.backgroundColor = UIColor(white: 0, alpha: 0.2) cardContainerView.addSubview(separatorView1) separatorView1.anchor(top: nil, left: cardContainerView.leftAnchor, bottom: cardContainerView.bottomAnchor, right: cardContainerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, height: 0.7, width: 0) addSubview(stpCardTextField) stpCardTextField.anchor(top: cardContainerView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 10, paddingLeft: 30, paddingBottom: 0, paddingRight: 30, height: 0, width: 0) addSubview(addButton) addButton.anchor(top: stpCardTextField.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 20, paddingLeft: 50, paddingBottom: 0, paddingRight: 50, height: 45, width: 0) addSubview(chooseLabel) chooseLabel.anchor(top: nil, left: leftAnchor, bottom: nil, right: rightAnchor, paddingTop: 0, paddingLeft: 10, paddingBottom: 0, paddingRight: 0, height: 0, width: 0) let separator = UIView() separator.backgroundColor = UIColor(white: 0, alpha: 0.2) addSubview(separator) separator.anchor(top: chooseLabel.bottomAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 2, paddingLeft: 5, paddingBottom: 0, paddingRight: 0, height: 0.5, width: 0) }2. ボタンを押しても反応しない
ボタン(UIButton)を設置したあとにそのボタンが機能していなかった時にもらったリジェクトです。
テスト環境ではうまく動いたのに本番では出来ていなかったというのがありました。3. 機能のデモ動画を用意してくれい
これはリリースから半年過ぎたあたりに、起きた出来事です。
WalCalはホスト登録や、クレジットカードの追加&購入など機能があり、レビューチームは登録しないので、それぞれのデモを用意してくれとの事。
シュミレーターでは不可なので、スクリーン録画でそれをYouTubeにあげて、そのリンクをApp Reviewチームに渡しました。QuickPlayerで録画して、MOVファイルでも可能です。
参考動画:https://reiver.blog/quicktime/4. アップルIDサインアップが必須です
2カ月ぶりのアプデをしようとした際に起きたリジェクト内容。
なんとサードパーティーサインインを実装する場合は必ずApple ID sign in を実装しないといけなくなりました。
実装までにちょいと時間がかかりました。Apple Reviewガイドライン4.8に記載されてあります。
https://developer.apple.com/jp/app-store/review/guidelines/#sign-in-with-apple5. 最低限機能をつけてくれい
ARKitの実装テストをしてみたく、WalCalのファイル内に入れました。
ただ、アプリでARKitコードは一切実装させなかったのですが、Reviewチームに“ARKitのコードがあるので、ARKitの最低限の機能を満たしてください“と言われてしまいレジェクトです。
対処法はただコードをコメントアウトしました。Apple Reviewガイドラインの4.2に記載されてあります。
https://developer.apple.com/jp/app-store/review/guidelines/#minimum-functionality最後にこれからiOSアプリ開発を考えている方へ
初めてアプリを申請した時は審査に通らずリジェクトされるんじゃないかとかなり緊張しました。
そして、しっかりリジェクトされました!笑
初申請の時に3回連続でリジェクトされました。ただiOSアプリ開発者はほぼ100%リジェクトされた経験があると思うので、皆が通る道だと思ってください。
申請する->リジェクトされる->修正し、再度申請する->リジェクトされる->修正し、再度申請する。->やっと通るこんな感じです。
審査が通るまで頑張るのみです?
- 投稿日:2020-07-30T16:42:01+09:00
UIViewControllerAnimatedTransitioningでカスタム画面遷移アニメーションを実装
はじめに
UIViewControllerAnimatedTransitioningを使ってカスタム画面遷移アニメーションを実装する方法になります。
検索した情報が古かったり、よくわからないところがあったので、現時点での実装方法とつまずいたところをまとめました。対象バージョン
- Xcode ver.11.6
- iOS ver 13.5.1
Present/DismissとPush/Popで実装方法が変わる
ViewController間で直接画面遷移する方法(Present/Dismiss)と、NavigationControllerを使用した画面遷移(Push/Pop)では実装方法が変わります。
Present/Dismissで画面遷移する場合
画面遷移イメージ
UIViewControllerAnimatedTransitioningを使って画面遷移アニメーション(Present/Dismiss)を上下に遷移するようにカスタマイズしてみた
— TatsunoriMorita@フリーエンジニア (@king_of_morita) July 30, 2020
# swift pic.twitter.com/VxJ6ScvIsv概要と注意点
最初にViewController間で直接画面遷移する方法になります。真ん中のボタンを押下すると上から遷移先の画面が降りて遷移します。
注意点としてはmodalPresentationStyleを.fullScreenにします。
.customだとUIViewControllerTransitioningDelegateのイベントが着火されないです。
UIViewControllerTransitioningDelegateは遷移先のViewControllerにデリゲートさせます。遷移元(FirstViewController)
import UIKit class FirstViewController: UIViewController { let btn = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green btn.frame = CGRect(x: 0, y: 0, width: 100, height: 100) btn.setTitle("FirstView", for: .normal) btn.center = view.center view.addSubview(btn) btn.addTarget(self, action: #selector(touch), for: .touchUpInside) } @objc func touch(sender: UIButton) { let vc = SecondViewController() vc.modalPresentationStyle = .fullScreen present(vc, animated: true, completion: nil) } }遷移先(SecondViewController)
UIViewControllerTransitioningDelegateのPresentとDismissで呼ばれるメソッド内でどちらが呼ばれたのかフラグを切り替えて、アニメーションする際の判定で使用しています。
import UIKit class SecondViewController: UIViewController { let animator = Animator() override func viewDidLoad() { super.viewDidLoad() self.transitioningDelegate = self view.backgroundColor = .orange let btn = UIButton(type: .system) btn.frame = CGRect(x: 0, y: 0, width: 100, height: 100) btn.setTitle("SecondView", for: .normal) btn.center = view.center view.addSubview(btn) btn.addTarget(self, action: #selector(touch), for: .touchUpInside) } @objc func touch(sender: UIButton) { dismiss(animated: true, completion: nil) } } extension SecondViewController: UIViewControllerTransitioningDelegate { func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { // Presentで呼ばれる animator.presenting = true return animator } func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { // Dismissで呼ばれる animator.presenting = false return animator } }カスタムアニメーション
PresentとDismissの際のアニメーションを実装しています。
import UIKit class Animator: NSObject, UIViewControllerAnimatedTransitioning { let movedDistance: CGFloat = 70.0 // 遷移元のviewのずれる分の距離 let duration = 0.3 var presenting = false // 遷移するときtrue(戻るときfalse) func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return duration } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from) let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) // 遷移するときと戻るときとで処理を変える if presenting { presentTransition(transitionContext: transitionContext, toView: toVC!.view, fromView: fromVC!.view) } else { dismissTransition(transitionContext: transitionContext, toView: toVC!.view, fromView: fromVC!.view) } } // 遷移するときのアニメーション func presentTransition(transitionContext: UIViewControllerContextTransitioning, toView: UIView, fromView: UIView) { let containerView = transitionContext.containerView containerView.insertSubview(toView, aboveSubview: fromView) // 遷移先のviewを画面の上部に移動させておく toView.frame = toView.frame.offsetBy(dx: 0, dy: -containerView.frame.size.height) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.05, options: .curveEaseInOut, animations: { () -> Void in // 遷移元のviewを下げながらalphaを暗くする fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.movedDistance) fromView.alpha = 0.7 // 遷移先のviewを画面全体に移動 toView.frame = containerView.frame }) { (finished) -> Void in // 変更をもとに戻してアニメーション終了 fromView.frame = fromView.frame.offsetBy(dx: 0, dy: -self.movedDistance) fromView.alpha = 1.0 transitionContext.completeTransition(true) } } // 戻るときのアニメーション func dismissTransition(transitionContext: UIViewControllerContextTransitioning, toView: UIView, fromView: UIView) { let containerView = transitionContext.containerView containerView.insertSubview(toView, belowSubview: fromView) // 遷移先のviewを画面の下部に移動させておく toView.frame = toView.frame.offsetBy(dx: 0, dy: containerView.frame.size.height) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.05, options: .curveEaseInOut, animations: { () -> Void in // 遷移元のviewを上げながらalphaを暗くする fromView.frame = fromView.frame.offsetBy(dx: 0, dy: -self.movedDistance) fromView.alpha = 0.1 // 遷移先のviewを画面全体に移動 toView.frame = containerView.frame }) { (finished) -> Void in // 変更をもとに戻してアニメーション終了 fromView.frame = fromView.frame.offsetBy(dx: 0, dy: self.movedDistance) // 元の位置に戻す fromView.alpha = 1.0 transitionContext.completeTransition(true) } } }Push/Pop(NavigationController)で画面遷移する場合
画面遷移イメージ
UIViewControllerAnimatedTransitioningを使って画面遷移アニメーション(Push/Pop)を上下に遷移するようにカスタマイズしてみた#Swift pic.twitter.com/LmitkZNDBj
— TatsunoriMorita@フリーエンジニア (@king_of_morita) July 30, 2020概要と注意点
画面遷移のアニメーションをPresent/Dismissと同じになります。
NavigationControllerを使用しているので、UIViewControllerTransitioningDelegateではなくUINavigationControllerのサブクラスとしてCustomNavigationControllerを作成し、
UINavigationControllerDelegateをを継承させてnavigationController(_:animationControllerFor:from: to:)でPush/Popの判定をしています。
アニメーション用のクラスとしてはPresent/Dismissで作成したAnimatorクラスをそのまま使用しています。遷移元(FirstViewController)
NavigationControllerのpushViewControllerを使用して遷移先を呼び出しています。
こちらではvc.modalPresentationStyle = .fullScreenは設定しなくてもいいようです。import UIKit class FirstViewController: UIViewController { let btn = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .green btn.frame = CGRect(x: 0, y: 0, width: 100, height: 100) btn.setTitle("FirstView", for: .normal) btn.center = view.center view.addSubview(btn) btn.addTarget(self, action: #selector(touch), for: .touchUpInside) } @objc func touch(sender: UIButton) { let vc = SecondViewController() navigationController?.pushViewController(vc, animated: true) } }遷移先(SecondViewController)
前の画面に戻るときはのpopViewControllerで遷移元を呼び出す。
import UIKit class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .orange let btn = UIButton(type: .system) btn.frame = CGRect(x: 0, y: 0, width: 100, height: 100) btn.setTitle("SecondView", for: .normal) btn.center = view.center view.addSubview(btn) btn.addTarget(self, action: #selector(touch), for: .touchUpInside) } @objc func touch(sender: UIButton) { navigationController?.popViewController(animated: true) } }NavigationControllerのカスタマイズ
UINavigationControllerのサブクラスとしてCustomNavigationControllerを作成し、UINavigationControllerDelegateを継承します。
そうすることで画面遷移(Push/Pop)が発生した際にnavigationController(_:animationControllerFor:from: to:)でアニメーションを指定できます。class CustomNavigationController: UINavigationController, UINavigationControllerDelegate { override init(rootViewController: UIViewController) { super.init(rootViewController: rootViewController) delegate = self } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { switch operation { case .push: let animator = Animator() animator.presenting = true return animator case .pop: let animator = Animator() animator.presenting = true return animator default: return nil } } }遷移元のNavigationControllerにCustomNavigationControllerを使用する
CustomNavigationControllerが使用できるように、遷移元であるFirstViewControllerをrootViewControllerに設定します。
今回はFirstViewControllerをSceneDelegateで呼び出しているのでそちらで設定します。import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: scene) self.window = window window.makeKeyAndVisible() window.rootViewController = CustomNavigationController(rootViewController: FirstViewController()) } // 省略 }まとめ
なかなか最近の情報が見当たらなく動くところまで行くのに時間がかかってしまいました。
なにか参考になれば嬉しいです。参考記事
- 投稿日:2020-07-30T16:38:13+09:00
Vision.Frameworkの顔検出を試してみた
はじめに
Vision.Frameworkでの顔検出(+少し顔のランドマーク検出)を試してみて、
取得できるパラメーターや現状やりたかったけどできなかったことをまとめました。
(iOS13まで)環境
Xcode 11.4.1
Swift 5今回は、フロントカメラを表示した際に顔検出を行いました。
実装方法は主にこちら(【iOS12対応】Visionを使って顔検出を行う)を参考にさせていただきました。顔検出について
request: VNDetectFaceRectanglesRequest
顔検出範囲
可能
- 横顔の場合
- 目と鼻が出ている場合(口をマスクなどで隠した場合)
- カメラの上下と顔の上下が一致しない場合
- 顔が(縦)半分しかカメラに写っていない場合
- 2mほどカメラから離れた場合
不可能
- マスクで鼻から下を隠した場合
- 顔上(下)半分がカメラに写っていない場合
取得できるパラメーター
- requestRevision
Visionのアルゴリズムのバージョン
1 : iOS11
2 : iOS12
- confidence
信頼度を示す
最小値が0、最大値が1.0何度も検証してみましたが、
固定値1.0で返ってくるらしいです。
- boundingBox = (x, y, height, width)
認識対象を包括する矩形
midX/Yも取得できます。
- roll(iOS12以上)
首の傾げ具合の角度
30度ずつ取得できる
(Radian表記 30度 = 0.5)
- yaw(iOS12以上)
顔の横向き角度
45度ずつ取得できる
(Radian表記 45度 = 0.75)不可能だったこと
- 顔の上下の角度の取得
AWSRekognisionを並行して使用していたのですが、
そちらを使用すると顔が下(上)を向いている角度を取得できていました。
Visionでは現状そのようなパラメーターは用意されていないようです。顔のランドマーク検出について
request : VNDetectFaceLandmarksRequest
こちらは軽く試してみました。
取得可能パラメーター
requestRevision / confidence / boundingBoxは顔検出と同じように取得できます。
- landmarks
顔検出のrequestで使おうとするとnilになるようです。
顔全体で76ポイントを取得しており、
左目、右眉、鼻、輪郭などパーツごとにCGPointの配列を取得できます。
詳しくはAppleDeveloperのドキュメントやこちらをご参照ください。
- landmarks.confidence
landmarks.confidenceは、
顔が正面の時に約0.9、真横を向くと約0.7が取得できました。不可能だったこと
- 写っていないパーツの検出
手で目を隠しているのに、目のポイントが検出されていました。
顔が検出されてしまうと写っていないパーツでも自動補完してくれているようです(?)写真を撮る時に写っていないパーツがないかチェックしたかったんですが、
現状Visionでは判定できなさそうです。
- (目を閉じている場合の検出)
がんばれば計算で検出できそうな感じもしました。(参考記事)
CIDetectorを使用すれば検出可能みたいです。さいごに
今回はVisionでの顔検出について調査してみて、
ざっくりと使えそうなところだけまとめてみました。
iOS14が出ることに伴い、またVisionでできることの範囲は広がりそうですね。実装方法はとても素晴らしい記事を書いている方がたくさんいらっしゃるので、
参考URLを見て是非試してみてください。初投稿記事なので、至らぬ点あるかと思いますが、
何かありましたらお気軽にコメントお願いいたします。参考
https://developer.apple.com/documentation/vision
- 顔検出について
- 顔のランドマーク検出について
- 投稿日:2020-07-30T14:53:57+09:00
自分的にiPhoneシミュレータでiOS開発する際に欠かせない機能8つ
iOS開発には欠かせないiPhoneシミュレータ。
これが非常に出来がよく、実機で出来ることのほとんどをシミュレートしてくれるので、正直自分は実機よりもこちらを使って開発することが多いです。
Mac上でサクッと動作確認できることはもちろん便利なのですが、ショートカットキーを駆使すればキーボードに手を乗せたままいろいろ操作出来るのがこれまた便利。
また、一見出来なさそうに見えるアレやコレも実はシミュレート出来ちゃいます。
というわけで、個人的に便利だと思っているiPhoneシミュレータの機能をまとめてみました。
1. iPhoneシミュレータを回転させるショートカットキー
「command + →」 で右回転、「command + ←で左回転」 できます。
回転がほぼ必須なiPadアプリを開発するときは特に重宝します。
2. 実は二本指のピンチも可能
optionキーを押しながらトラックパッドに二本指を置いて片方の指を動かすとピンチできます。 (シミュレータ上に灰色のボタンが二つ表示されたら可能)
さすがにジェスチャ関係は実機を使った方がラクですが、サクッと確認したいときに結構重宝しています。
3. Color Blended Layers
レイヤーが重なっていればいるほど赤く表示してくれる「Color Blended Layers」ですが、 自分は実際のUIパーツのサイズがどうなっているのかを確認するときによく使います。
もちろん、ちゃんと見る場合はヒエラルキーを使った方が良いですが、サクッと確認したいときには重宝しますね。
「メニューバー > Debug > Color Blended Layers」 で表示できます。
4. キーボードを表示・非表示するショートカットキー
UITextFieldもしくはUITextViewにカーソルを置いて、「command + K」 を叩くとキーボードを表示・非表示できます。
なぜか、シミュレータだとキーボードが表示されない場合って結構あるのでこれを覚えておくと便利です。
5. 全てのデータを消去
「メニューバー > Hardware > Erase All Content and Settings...」 で現在使用しているシミュレータの全データを削除できます。常識的なやつですが念の為。
6. ホーム画面に戻るショートカットキー
「shift + command + H」 でホーム画面に戻ることができます。
ちなみに、 「shift + command + H」を二連打すればアプリスイッチャーに遷移できるのでこれまた便利。 捗ります。
7. FaceIDもシミュレートできる
実はFaceIDもシミュレートできます。
まあ、FaceIDは実機を使った方がラクなのですが手元に実機がない場合は重宝します。「メニューバー > Hardware > FaceID」 から。
各項目の意味は以下の通り。
- Enrolled(FaceIDを使える端末として認識させる)
- Matching Face(FaceID認証が通ったことをシミュレート)
- Non-matching Face(FaceID認証が通らないことをシミュレート)
8. 位置情報を指定の場所にする
位置情報も指定できちゃいます。
「メニューバー > Debug > Location > Custom Location...」 で緯度経度を指定できます。
いや、「位置情報の開発はさすがに実機使うよ」という方は多いと思いますが、シミュレータは どこでも 行けるんです。
そう。例えばクパチーノでも。イタリアでも。もちろん北...極でも。
最後に
全部知っている方は少なくないと思いますが、自分がこれからiOS開発を始めるとしたらこれらを知ってると便利だと思うので書いておきます。
もし、この内のいくつかが参考になればこれ幸いです。
- 投稿日:2020-07-30T12:16:02+09:00
Dock上のアイコンにファイルをドラッグし、アプリ側で受け取る方法
概要
- Dock上のアイコンにファイルをドラッグし、アプリ側で受け取る方法を紹介します。
参考
- iOS 11からのアプリ間ファイル共有
Document Types
・Exported and Imported UTIs
などの全体の解説がありがたいです。- Dropping Files onto Dock Icon in Cocoa
- Document Types vs. Exported and Imported UTIs
- [iOS]今からはじめるドキュメントベースApp - Qiita
- MIMEとは
実装
- 注意: 設定に問題ないのに動作が変更されない、というときは一度クリーンアップ(
Shift + Command + k
)しましょう。- まずは
TAEGETS
>Document Type
に下記の通り追加します。
Name
は任意ですね。identifier
に目的のUTIを記述します。- 複数ある場合はカンマ区切りで記述します。
- UTIを確認したい場合は以下を参照。
- ちなみに
info.plist
的には以下の通りになります。- ただしplistを直接編集したときに、上記画面との同期が怪しいです。
- 一旦Xcodeを閉じないとうまく行かなかったりするので、
info.plist
を直接編集するのは避けたほうが良さそうです。<key>CFBundleDocumentTypes</key> <array> <dict> <key>CFBundleTypeExtensions</key> <array/> <key>CFBundleTypeIconFile</key> <string></string> <key>CFBundleTypeName</key> <string>ImageType</string> <key>CFBundleTypeRole</key> <string>Editor</string> <key>LSItemContentTypes</key> <array> <string>public.jpeg</string> <string>public.png</string> </array> </dict> <dict/> </array>
- または
Extensinons
に開きたいファイルの拡張子を設定することも可能です。- ただし問題点として以下の例では
*.JPG
は受け取れますが*.jpeg
が受け取れません。
jpeg
をExtensions
に追加すればいいですが網羅することが難しくなるので、やはりUTIで指定するのが便利そうですね。
- 最後に
AppDelegate.swift
に以下を記述します。- それぞれ、単ファイル・複数ファイルを受け付けた場合の処理をここに記述できます。
func application(_ sender: NSApplication, openFile filename: String) -> Bool { print(filename) return true } func application(_ sender: NSApplication, openFiles filenames: [String]) { print(filenames) }
- 以上でファイルをDock上のアイコンにドロップして受け取れるようになりました。
- 投稿日:2020-07-30T10:22:46+09:00
Swift:macOSの環境設定の各項目に飛ぶ
基本的にはこのStackOverflowの記事の通り
ワンポイントとして、
NSWorkspace.shared.open(URL(fileURLWithPath: "x-apple.systempreferences:com.apple.preference"))
のようにすると飛べるとあるが、これは実際には動かない。
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference")!)
なら飛べた。URL(fileURLWithPath: )だとあくまでファイルへのパスになってしまうのかもしれない。
- 投稿日:2020-07-30T10:17:53+09:00
Xcode11で作成したプロジェクトをiOS12に対応させる方法
はじめに
久しぶりに新規にプロジェクトを作ったのですが、iOS12で動かなかったのでその対応方法です。
Xcode11から SceneDelegate というものがテンプレートで生成されるようになりました。iOS13以降で同一アプリを複数表示するSceneという概念が導入されたことに伴う変更なのですが、iOS12ではSceneの概念がないためクラッシュします。
そのため、iOS12に対応させるためにはひと手間必要になってしまいました。iPhone5sやiPhone6など、まだまだ現役なので対応させない手はありませんよね。開発環境
- Xcode11.5
- Swift
対応内容
AppDelegate
UISceneSession Lifecycleの2つのメソッドをiOS13以降のみビルドされるように修正します。また、SceneDelegateで定義されるようになったUIWindowをAppDelegateに追加します。
AppDelegate.swift@UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? // この行を追加 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. return true } // MARK: UISceneSession Lifecycle @available(iOS 13.0,*) // この行を追加 func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } @available(iOS 13.0,*) // この行を追加 func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } }SceneDelegate
SceneDelegateはiOS12に対応していないため、クラスごとiOS13以降にします、
SceneDelegate.swift@available(iOS 13.0,*) // この行を追加 class SceneDelegate: UIResponder, UIWindowSceneDelegate { : }最後に
プロジェクトをゼロから作ることが案外少ないため、久しぶりにテンプレートから作成したらいきなり動かなくてびっくりしました。
iOSは進化が早いというか、後方互換性が切り捨てられるというか、ついていくのが大変ですね。
- 投稿日:2020-07-30T08:14:47+09:00
Firestoreからデータが取得できなくなった時。Error Code=7 "Missing or insufficient permissions.
エラー:Firestoreからデータの取得ができなくなった
version
Swift5
Xcode11.5
target iOS v13.4エラーコード
Error Domain=FIRFirestoreErrorDomain Code=7 "Missing or insufficient permissions."
これは、"ドメインを間違えている"or"許可が不十分"
というエラー文になります。
この問題を解決するには、Firestore へアクセスし、
自分のプロジェクトを選択 > データベース > ルール
を選択します。すると、以下のようなコードが表示されているかと思います。rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // This rule allows anyone on the internet to view, edit, and delete // all data in your Firestore database. It is useful for getting // started, but it is configured to expire after 30 days because it // leaves your app open to attackers. At that time, all client // requests to your Firestore database will be denied. // // Make sure to write security rules for your app before that time, or else // your app will lose access to your Firestore database match /{document=**} { allow read, write: if request.time < timestamp.date(2020, 8, 10); } } }下から4行目がエラーの原因になっています。
if request.time < timestamp.date(2020, 8, 10);このtimestamp.date(2020, 8, 10);の部分が有効期限です。
セキュリティを考慮し、初回登録時にはこのように短めの有効期限が
自動で記述されています。
この部分を現在の日付よりも先に設定し、公開してあげると、
再度データの取得ができるようになります。他の記事には〜〜〜=nil;
のように記述するよう回答しているものもありますが、
セキュリティ上お勧めしないと公式サイトでも述べられています。
個人的にはこの記事で紹介したように
自分で有効期限を記述してデータを取得してください。
- 投稿日:2020-07-30T08:14:47+09:00
Firestoreからデータが取得できなくなった。Error Code=7 "Missing or insufficient permissions.
エラー:Firestoreからデータの取得ができなくなった
version
Swift5
Xcode11.5
target iOS v13.4エラーコード
Error Domain=FIRFirestoreErrorDomain Code=7 "Missing or insufficient permissions."
これは、"ドメインを間違えている"or"許可が不十分"
というエラー文になります。
この問題を解決するには、Firestore へアクセスし、
自分のプロジェクトを選択 > データベース > ルール
を選択します。すると、以下のようなコードが表示されているかと思います。rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // This rule allows anyone on the internet to view, edit, and delete // all data in your Firestore database. It is useful for getting // started, but it is configured to expire after 30 days because it // leaves your app open to attackers. At that time, all client // requests to your Firestore database will be denied. // // Make sure to write security rules for your app before that time, or else // your app will lose access to your Firestore database match /{document=**} { allow read, write: if request.time < timestamp.date(2020, 8, 10); } } }下から4行目がエラーの原因になっています。
if request.time < timestamp.date(2020, 8, 10);このtimestamp.date(2020, 8, 10);の部分が有効期限です。
セキュリティを考慮し、初回登録時にはこのように短めの有効期限が
自動で記述されています。
この部分を現在の日付よりも先に設定し、公開してあげると、
再度データの取得ができるようになります。他の記事には〜〜〜=nil;
のように記述するよう回答しているものもありますが、
セキュリティ上お勧めしないと公式サイトでも述べられています。
個人的にはこの記事で紹介したように
自分で有効期限を記述してデータを取得してください。
- 投稿日:2020-07-30T06:17:04+09:00
NaturalLanguageフレームワークを使用して日本語のための自然言語処理
Appleには、テキストの分析を支援する
NaturalLanguage
というフレームワークがあります。この記事は、日本語テキスト分析のための自然言語フレームワークがサポートする、いくつかの機能について取り上げます。import NaturalLanguage
- テキストの言語を検知
- 文を単語区分に分割
この例では、入力テキストは以下のとおりです。
データの保存はiOSアプリの持つ主要な機能です。たとえば、ユーザーが指定した色などの環境設定を保存したり、ウェブサイトのトークンをアプリに保存したり、ToDoリストのアプリを作ってタスクを保存したりすることができます。データをシステムに保存する方法はいくつもあります。言語検知
このコマンドを実行して、文の言語を検知できます。
func detectLanguage(text: String) { let recognizer = NLLanguageRecognizer() recognizer.processString(text) print("Language: \(recognizer.dominantLanguage?.rawValue ?? "unknown")") }結果はこのとおりです。
Language: jaトークンへの分解
このコマンドで文を単語に分解します。
func tokenize(text: String) { let tokenizer = NLTokenizer(unit: .word) tokenizer.string = text let tokens = tokenizer.tokens(for: text.startIndex ..< text.endIndex) var textTokens: [String] = [] for token in tokens { let tokenStartI = token.lowerBound let tokenEndI = token.upperBound let text = text[tokenStartI ..< tokenEndI] textTokens.append(String(text)) } print(textTokens) }結果はこのとおりです。
["データ", "の", "保存", "は", "iOS", "アプリ", "の", "持つ", "主要", "な", "機能", "です", "たとえば", "ユーザー", "が", "指定", "し", "た", "色", "など", "の", "環境", "設定", "を", "保存", "し", "たり", "ウェブサイト", "の", "トークン", "を", "アプリ", "に", "保存", "し", "たり", "ToDo", "リスト", "の", "アプリ", "を", "作っ", "て", "タスク", "を", "保存", "し", "たり", "する", "こと", "が", "でき", "ます", "データ", "を", "システム", "に", "保存", "する", "方法", "は", "い", "くつ", "も", "あり", "ます"]
NaturalLanguage
フレームワーク内には他の機能もあります。試してみましたが、英語でのみ利用可能なものもありました。より多くの機能が利用可能になりましたら、この記事を更新します。
- 投稿日:2020-07-30T01:38:02+09:00
RealityKitで新規プロジェクト作成し動画テクスチャを設定する
WWDC2020で Video materials が発表されました。こちらの動画 "What's new in RealityKit" で映像付きで解説されています。RealityKitでオブジェクトに動画を設定し再生できる新機能です。
この機能だけ試したくなりましたが、RealityKitで新規プロジェクト作成すると
Experience.rcproject
を読み込んで別シーンを表示する構成で簡単には試せません。そこでシンプルに平面を配置し動画を流すまでの部分を切り出してみました。AR空間に配置されたPlaneで自動的に動画を再生します。
ソースコードは こちら に置いています。環境
- Xcode 12.0 beta 2
- iPadOS 14 beta
- iPad Pro 11 inch (第2世代)
プロジェクト作成
今回は
Content Technology
をRealityKit
で、Interface
をStoryboard
で作成しました。
video001.mp4
をプロジェクトに追加しておきます。実装
ソース全体
ViewController.swiftimport UIKit import RealityKit import AVFoundation class ViewController: UIViewController { @IBOutlet private var arView: ARView! override func viewDidLoad() { super.viewDidLoad() // ① Anchorの追加 let anchor = AnchorEntity() arView.scene.anchors.append(anchor) // ② Playerの生成と動画再生 let asset = AVURLAsset(url: Bundle.main.url(forResource: "video001", withExtension: "mp4")!) let playerItem = AVPlayerItem(asset: asset) let player = AVPlayer() player.replaceCurrentItem(with: playerItem) player.play() // ③ Plane生成とVideoMaterial設定 let planeMesh = MeshResource.generatePlane(width: 0.8, height: 0.45) let material = VideoMaterial(avPlayer: player) let planeModel = ModelEntity(mesh: planeMesh, materials: [material]) planeModel.position = SIMD3<Float>(0.0, 0.0, -1.0) anchor.addChild(planeModel) } }① Anchorの追加
ARViewに対してAnchorを追加。
② Playerの生成と動画再生
再生対象の動画から
AVPlayerItem
を生成。AVPlayer
に設定しplay()
で再生開始。③ Plane生成とVideoMaterial設定
MeshResource.generatePlane
でPlaneを生成。数値は現実世界でメートルです。これにより「0.8m x 0.45m の横長の板」が生成されます。VideoMaterial
を生成後、ModelEntity
でEntity
生成。positionはzに-1.0を設定し「1メートル手前」を設定。anchorにaddChild
で追加。この状態で起動すると
無事再生されました!
- 投稿日:2020-07-30T00:14:26+09:00
【Swift】テキストに取り消し線を付ける
NSAttributeString
を使って文字列を装飾するやり方をまとめました。完成図
実装
@IBOutlet weak var textLabel: UILabel! private func setTestLabel() { // 表示したいテキスト let text = "文章の中の一部に取り消し線を付けたい。" let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: text) // 全体に共通して付けたいレイアウトを設定 attributeString.addAttribute(.font,value: UIFont.systemFont(ofSize: 15), range: NSMakeRange(0, attributeString.length)) // 取り消し線部分の設定 attributeString.addAttributes([ .foregroundColor : UIColor.red, // 取り消し線の太さを決める .strikethroughStyle: 1 // 取り消し線を反映したい部分を設定 // NSMakeRange(何文字目から, 何文字間) ], range: NSMakeRange(8, 5)) textLabel.attributedText = attributeString } // NSMakeRange(何文字目から, 何文字間)はこんな感じにも書き換えられる // こちらの方が直感的でわかりやすい NSString(string: text).range(of: "取り消し線")これで取り消し線の実装は完了です?