20200730のSwiftに関する記事は15件です。

【Swift 】[String]()とは

var interestId = String

変数宣言でよくみるこの配列の後ろについた()

そいやあこいつ、なんで()がついてんの?

って思ったので、忘備録として記録しておきます。

Swiftでの配列の書式とコードの書き方

空で初期化する場合は下記の書き方をします。
var 配列変数名:[型名] = []
var 配列変数名 = [型名]()

ほーなるほど。

var 配列変数名:[型名] = []

と書いても同じなんですね。

空で初期化する時にこの書き方をするらしいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[swift]MVCモデルに関して簡潔にまとめました。

MVCモデルとは

Model,View,Controllerの頭文字をとった略称でそれぞれのフォルダを作成してプログラムの構成を他者が見た場合でもわかりやすくする手法。

この手法は多くのプログラマに使用されており、共通のストラクチャーとして認識してもらいやすい。
また、分業して作業する事ができる点や、変更や修正があった場合に影響を受けにくいなどというメリットが存在する。

Model

機能に関する処理を行う。(データの処理を行う)

View

表示と、ユーザーからのアクションを受ける処理を行う。

Controller

機能とアクションを用いてViewに見せる処理を行う。

最後に

今回はMVCモデルに関してかなり簡潔にまとめました。
是非参考にしてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftとFirebaseでSNSアプリNomadを開発しよう!(第0回)

どんなアプリを作るのか

「SNSアプリをSwiftで作ってみたいけどどのように作ればいいか分からない、、、」
「どのようにアプリをリリースさせればいいか分からない、、、」

このようにお悩みの方は多いと思います。実際私もSwiftを学び始めた頃は自分が理想とするアプリなんて作れるはずがないと思っていました。(単純な画面遷移すら苦労していました笑)
しかし、自分なりにSwiftを学んでみて、この度「Nomad」というアプリを開発し、App Storeにリリースすることができました。Nomadは趣味で繋がれるコミュニティ型のサービスであり、共通の話題さえあればトークできる点が特徴です。
つまり、年齢も経歴も性別も思想も関係なく、誰でも平等な関係でつながれます。これは、昨今問題となっている新型コロナウイルスの流行から着想を得ました。多くの人々が外出自粛を余儀なくされ、閉鎖的な空間にいる現状のなか、「誰かとつながりたい」「ぬくもりを感じたい」と思う人々の欲求に寄り添ったサービスをつくろうと思い、サービスの開発に取り組みました。

このアプリの作り方を共有して、少しでもiOSアプリを開発しようとしている皆さんの参考になれば嬉しいです!
なお、私自身もまだまだ未熟な点が多々あるので、「こういう風にしたらいいんじゃない?」といった指摘がございましたら、どんどんコメントをしていただけると嬉しいです!

では、次回から早速開発をしていきます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSアプリ初リリースからこれまでにApp Store Reviewチームからリジェクトされた理由5選

私の最初のアプリ(WalCal)をAppStoreにリリースしてから、約9ヶ月が経ちました。リリースから今まで計7回AppleReviewの審査リジェクトされました。今回はそのリジェクト内容を紹介します。

1. 画像が鮮明ではない

WalCal(ウォーカル)の決済画面に利用可能なクレジットカード会社(VISA, JCB, MasterCard)のロゴ(UIImageView)をStackviewを使い表示しているのですが、これが全体的に伸びてしまったいたためでデザイン面でApple Reviewチームからリジェクトを食らいました。
IMG_2339_iphonesespacegrey_portrait.png

対処法はUIImageViewのContentModeをScaleAspectFillで設定していたのを、scaleAspectFitに変更しました。
IMG_2340_iphonesespacegrey_portrait.png

以下がコードです。

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-apple

5. 最低限機能をつけてくれい

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%リジェクトされた経験があると思うので、皆が通る道だと思ってください。
申請する->リジェクトされる->修正し、再度申請する->リジェクトされる->修正し、再度申請する。->やっと通る

こんな感じです。

審査が通るまで頑張るのみです?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UIViewControllerAnimatedTransitioningでカスタム画面遷移アニメーションを実装

はじめに

UIViewControllerAnimatedTransitioningを使ってカスタム画面遷移アニメーションを実装する方法になります。
検索した情報が古かったり、よくわからないところがあったので、現時点での実装方法とつまずいたところをまとめました。

対象バージョン

  • Xcode ver.11.6
  • iOS ver 13.5.1

Present/DismissとPush/Popで実装方法が変わる

ViewController間で直接画面遷移する方法(Present/Dismiss)と、NavigationControllerを使用した画面遷移(Push/Pop)では実装方法が変わります。

Present/Dismissで画面遷移する場合

画面遷移イメージ

概要と注意点

最初に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)で画面遷移する場合

画面遷移イメージ

概要と注意点

画面遷移のアニメーションを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())
    }
    // 省略
}

まとめ

なかなか最近の情報が見当たらなく動くところまで行くのに時間がかかってしまいました。
なにか参考になれば嬉しいです。

参考記事

https://qiita.com/kitoko552/items/4c0e411ff6224090db87

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分的にiPhoneシミュレータでiOS開発する際に欠かせない機能8つ

iOS開発には欠かせないiPhoneシミュレータ。

これが非常に出来がよく、実機で出来ることのほとんどをシミュレートしてくれるので、正直自分は実機よりもこちらを使って開発することが多いです。

Mac上でサクッと動作確認できることはもちろん便利なのですが、ショートカットキーを駆使すればキーボードに手を乗せたままいろいろ操作出来るのがこれまた便利。

また、一見出来なさそうに見えるアレやコレも実はシミュレート出来ちゃいます。

というわけで、個人的に便利だと思っているiPhoneシミュレータの機能をまとめてみました。

1. iPhoneシミュレータを回転させるショートカットキー

「command + →」 で右回転、「command + ←で左回転」 できます。

回転がほぼ必須なiPadアプリを開発するときは特に重宝します。

iPhoneシミュレータを回転させるショートカットキー

2. 実は二本指のピンチも可能

optionキーを押しながらトラックパッドに二本指を置いて片方の指を動かすとピンチできます。 (シミュレータ上に灰色のボタンが二つ表示されたら可能)

さすがにジェスチャ関係は実機を使った方がラクですが、サクッと確認したいときに結構重宝しています。

実はiPhoneシミュレータは二本指のピンチも可能

3. Color Blended Layers

レイヤーが重なっていればいるほど赤く表示してくれる「Color Blended Layers」ですが、 自分は実際のUIパーツのサイズがどうなっているのかを確認するときによく使います。

Screen Shot 2020-07-30 at 14.23.12.png

もちろん、ちゃんと見る場合はヒエラルキーを使った方が良いですが、サクッと確認したいときには重宝しますね。

「メニューバー > Debug > Color Blended Layers」 で表示できます。

4. キーボードを表示・非表示するショートカットキー

UITextFieldもしくはUITextViewにカーソルを置いて、「command + K」 を叩くとキーボードを表示・非表示できます。

なぜか、シミュレータだとキーボードが表示されない場合って結構あるのでこれを覚えておくと便利です。

iPhoneシミュレータでキーボードを表示・非表示するショートカットキー

5. 全てのデータを消去

「メニューバー > Hardware > Erase All Content and Settings...」 で現在使用しているシミュレータの全データを削除できます。常識的なやつですが念の為。

6. ホーム画面に戻るショートカットキー

「shift + command + H」 でホーム画面に戻ることができます。

ちなみに、 「shift + command + H」を二連打すればアプリスイッチャーに遷移できるのでこれまた便利。 捗ります。

iPhoneシミュレータでキーボードを表示・非表示するショートカットキー iPhoneシミュレータでキーボードを表示・非表示するショートカットキー

7. FaceIDもシミュレートできる

実はFaceIDもシミュレートできます。
まあ、FaceIDは実機を使った方がラクなのですが手元に実機がない場合は重宝します。

「メニューバー > Hardware > FaceID」 から。

各項目の意味は以下の通り。

  • Enrolled(FaceIDを使える端末として認識させる)
  • Matching Face(FaceID認証が通ったことをシミュレート)
  • Non-matching Face(FaceID認証が通らないことをシミュレート)

8. 位置情報を指定の場所にする

位置情報も指定できちゃいます。

「メニューバー > Debug > Location > Custom Location...」 で緯度経度を指定できます。

Screen Shot 2020-07-30 at 14.22.09.png

いや、「位置情報の開発はさすがに実機使うよ」という方は多いと思いますが、シミュレータは どこでも 行けるんです。

そう。例えばクパチーノでも。イタリアでも。もちろん北...極でも。

最後に

全部知っている方は少なくないと思いますが、自分がこれからiOS開発を始めるとしたらこれらを知ってると便利だと思うので書いておきます。

もし、この内のいくつかが参考になればこれ幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dock上のアイコンにファイルをドラッグし、アプリ側で受け取る方法

概要

  • Dock上のアイコンにファイルをドラッグし、アプリ側で受け取る方法を紹介します。

image

参考

実装

-w824

  • ちなみに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が受け取れません。
    • jpegExtensionsに追加すればいいですが網羅することが難しくなるので、やはりUTIで指定するのが便利そうですね。

func application(_ sender: NSApplication, openFile filename: String) -> Bool {
    print(filename)
    return true
}

func application(_ sender: NSApplication, openFiles filenames: [String]) {
    print(filenames)
}
  • 以上でファイルをDock上のアイコンにドロップして受け取れるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: )だとあくまでファイルへのパスになってしまうのかもしれない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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は進化が早いというか、後方互換性が切り捨てられるというか、ついていくのが大変ですね。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
のように記述するよう回答しているものもありますが、
セキュリティ上お勧めしないと公式サイトでも述べられています。
個人的にはこの記事で紹介したように
自分で有効期限を記述してデータを取得してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
のように記述するよう回答しているものもありますが、
セキュリティ上お勧めしないと公式サイトでも述べられています。
個人的にはこの記事で紹介したように
自分で有効期限を記述してデータを取得してください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 フレームワーク内には他の機能もあります。試してみましたが、英語でのみ利用可能なものもありました。より多くの機能が利用可能になりましたら、この記事を更新します。

こちらのウェブページにアクセスすると、私の公開されているQiitaの記事のリストをカテゴリー別にご覧いただけます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RealityKitで新規プロジェクト作成し動画テクスチャを設定する

WWDC2020で Video materials が発表されました。こちらの動画 "What's new in RealityKit" で映像付きで解説されています。RealityKitでオブジェクトに動画を設定し再生できる新機能です。

この機能だけ試したくなりましたが、RealityKitで新規プロジェクト作成すると Experience.rcproject を読み込んで別シーンを表示する構成で簡単には試せません。そこでシンプルに平面を配置し動画を流すまでの部分を切り出してみました。

output2.gif

AR空間に配置されたPlaneで自動的に動画を再生します。
ソースコードは こちら に置いています。

環境

  • Xcode 12.0 beta 2
  • iPadOS 14 beta
  • iPad Pro 11 inch (第2世代)

プロジェクト作成

今回は Content TechnologyRealityKit で、InterfaceStoryboardで作成しました。

スクリーンショット 2020-07-29 3.22.30.png

video001.mp4 をプロジェクトに追加しておきます。

output.gif

実装

ソース全体

ViewController.swift
import 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を生成後、ModelEntityEntity生成。positionはzに-1.0を設定し「1メートル手前」を設定。anchorにaddChildで追加。

この状態で起動すると

output2.gif

無事再生されました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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: "取り消し線")

これで取り消し線の実装は完了です?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む