20200917のSwiftに関する記事は5件です。

対象(ImageView)の回転について

はじめに

iPhoneアプリ開発における目標物の回転についての記事を書こうと思います。
先日、私事ではありますが自身初のiPhoneアプリであるルーレットアプリをAppStoreの方に公開させていただきました。
その際にImageViewの回転について実装したので、自分で思い出す意味でも記事にしておこうと思います。

実装

回転を実装するにはanimateプロパティにおいて、"CGAffineTransform"を用います。CGAffineTransform(rotationAngle:CGFloat.回転角)とすることで実装できます。

viewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var rouletteView: UIImageView!
    @IBOutlet weak var button: UIButton!

    func rotation() {
        UIView.animate(withDuration: 1, animations: {

            self.rouletteView.transform = CGAffineTransform(rotationAngle: CGFloat.pi)

            })

    }

    @IBAction func go(_ sender: Any) {
        rotation()
    }

}

以上のように記述すると、回転角が pi = 180°となっているので半回転します。
ezgif.com-gif-maker.gif
※ここで注意したいのが、角度の指定にはpiを用いなければならないので、

viewController.swift
    self.rouletteView.transform = CGAffineTransform(rotationAngle: CGFloat.45)

などとするとエラーが返されます。
なので、細かい角度まで調整したいときは、

viewController.swift
    self.rouletteView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/4)
    self.rouletteView.transform = CGAffineTransform(rotationAngle: 45*CGFloat.pi/180)//などでも正常に動作します。

と記述すれば動作します。
ezgif.com-gif-maker (1).gif

もう一つ問題があります。
例えば、以下のように240°回転させるような記述をしたとします。

viewController.swift
    self.rouletteView.transform = CGAffineTransform(rotationAngle: 240*CGFloat.pi/180)

すると...
ezgif.com-gif-maker (2).gif
このように、120°逆回転しています。
この記述は、確かに240°回転するように書かれています。ただ、機械からすると240°回転したときの"到達点"への回転移動を行っていることになります。したがって、240°右回転するよりも120°左に回ってしまった方が、速く処理できますから左回転してしまうわけです。

ということはもちろん以下の記述をしたときは...

viewController.swift
    self.rouletteView.transform = CGAffineTransform(rotationAngle: 360*CGFloat.pi/180)

sample.png
見事ですね!微動だにしません笑

解決方法

では、181°~360°での右回転はどのようにしたらいいのかというと...

viewController.swift
    self.rouletteView.transform = CGAffineTransform(rotationAngle: 180*CGFloat.pi/180)
    self.rouletteView.transform = CGAffineTransform(rotationAngle: 240*CGFloat.pi/180)

このように、2回に分けて処理を行えば動作します。1行目の
self.rouletteView.transform = CGAffineTransform(rotationAngle: 180*CGFloat.pi/180)で
半回転させてしまえば、そこ(180°)の地点から181°~360°の地点は右回転が最短ルートになるので、そのまま右回転を続けるというわけです。
ezgif.com-gif-maker (3).gif

また、ルーレットのように複数回回転するような場合は、以下のように制御構文を用いてあげれば問題ないです。

viewController.swift
    func rotation() {
        UIView.animate(withDuration: 1.5, animations: {

            let number = Int.random(in: 1..<360)
            for _ in 0...2{
                self.rouletteView.transform = CGAffineTransform(rotationAngle: 180*CGFloat.pi/180)
                self.rouletteView.transform = CGAffineTransform(rotationAngle: 360*CGFloat.pi/180)//360で1回転
            }

            self.rouletteView.transform = CGAffineTransform(rotationAngle: CGFloat.pi/180*CGFloat(number))

        })

    }

ezgif.com-gif-maker (4).gif

まとめ

今回、初めてのQiita投稿になったので思っていたよりも時間がかかってしまいましたが、なんとか投稿できてよかったです。文章やコードなど、おかしい点などありましたらコメントいただけると助かります。
また、iOSは14.0にアップデートされXcodeも12.0へバージョンアップしたのでできるだけ速く順応できるようにがんばりたいです!

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

Flutter で Swift のライブラリがリンクされずにビルド失敗する

問題

Flutter でプラグイン(ネイティブ実装)を含む新しいパッケージを使うようにしたら、突然 Swift 関係のリンクエラーでビルド失敗することがあります。

    ld: symbol(s) not found for architecture arm64
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    ld: warning: Could not find or use auto-linked library 'swiftCoreLocation'
    ld: warning: Could not find or use auto-linked library 'swiftCoreFoundation'
    ld: warning: Could not find or use auto-linked library 'swiftCompatibility50'
    ld: warning: Could not find or use auto-linked library 'swiftFoundation'
...

原因

flutter create -i objc で作成した Flutter アプリから、Swift コードを含むパッケージを参照すると、この問題が発生します。Swift 関係のライブラリの参照設定が全く行われていないからです。

flutter create 時に Swift (デフォルト)が選択されていればこの問題は発生しません。

解決

Flutter の Runner ワークスペース(ios/Runner.xcworkspace) を開いて、何か1つ Swift のコードを追加してやれば治ります。

  1. Xcode を使って、ios/Runner.xcworkspace を開きます。
  2. New File... メニューを使って何か1つダミーの Swift ファイルを追加します。
  3. 「Swift - ObjC 間の Briding-Header.h を作るか」と尋ねられるので、「YES」を選択してヘッダを生成させます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cocoapodsのアンインストール方法

uninstall.png

(1)Macのコマンドを使って
自分が作成しているアプリのファイルの場所まで進む

(2)以下を入力してEnter
「gem list --local| grep cocoapods」

Cocoapodsのバージョンやファイルの一覧がでてきます。
(写真参照願います)

(3)cocoapodsのバージョンやファイル名が確認できたら以下を入力してEnter

sudo gem uninstall 「削除したいcocoapodsのファイル名」

僕の場合は8つのファイルをuninstallしました
(写真に赤線を引っぱっておきました)

以上です。

※「青」で塗りつぶしてあるところは
自分のmacの名前と 現在作成しているアプリのファイル名になります。

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

【iOS 14】SKOverlay を使ってオススメ/関連アプリを紹介する UI をお手軽実装

はじめに

時事雑談新しい iPhone の発売が遅れるというアナウンスもあって,
iOS 14 のリリースの仕方が気になっていましたが,
9/15 のイベントの中でかなり急な感じでリリース発表がありました。
(えっ明日!? Xcode 12 GM 出てたっけ?)
iOS 14 リリース日に合わせてアプリの配信を。と
考えていたデベロッパも多いはずで間に合うことを祈りたいですね・・・

iOS 14 で別のアプリをユーザーに薦め,すぐに App Store でインストール可能にする SKOverlay が使えるようになりました。

実装自体とても簡単で,UIKit,SwiftUI 両方で使えます。
自分の作った別のアプリを紹介するという形で組み込んでみたので
実装していて気づいた点を含め紹介します。
全体の動きはこんな感じです。

SKOverlay_00

SKOverlay とは

<iOS>-<iOS 14.0 and later> <Xcode>-<Xcode 12.0 and later>

Developer サイト1 には下記のように説明があります。

By displaying an overlay, you can recommend another app to users and enable them to download it immediately.

オススメしたいアプリやコンテンツに関連するアプリなどを表示させ,
App Store へ遷移させることでユーザがすぐにアプリ情報にアクセス,
アプリインストールが可能になります。

今まで App Store へのリンクや UI を用意していたのが,
ほんの少しの実装で代わりになってくれます。
iOS 14 以上対応なので,iOS 13 以下は別途対応が必要です。
(私はあえて iOS 13 以下は何もしないことにしました。)

アプリのレビュー促進(SKStoreReviewController2)など
簡単な実装で実現できる便利で Good な機能あるので StoreKit いいですよね。

SwiftUI

StoreKitimport します。
指定された条件が true の場合に表示されます。
例えばボタンタップで表示させたりができます。

import SwiftUI
import StoreKit // 追加

struct ContentView: View {
    // SKOverlayを表示するかの状態
    @State private var showSKOverlay = false

    var body: some View {
        Button("オススメアプリを表示") {
            self.showSKOverlay.toggle()  // 状態切替
        }
        .appStoreOverlay(isPresented: $showSKOverlay) {
            SKOverlay.AppConfiguration(appIdentifier: "1234567890", position: .bottom)
        }
    }
}

appIdentifier は App Store のアプリページのリンクの
末尾の id を除いた数字部分を文字列にしたもので,
https://apps.apple.com/jp/app/id1234567890
だったら "1234567890" です。

表示位置は 2パターンあり,.bottom.bottomRaised です。
後者は少し上に上がって表示されます。TabBar とか ToolBar を意識しているのかな?

.bottom .bottomRaised
スクリーンショット 2020-09-16 5 53 38 スクリーンショット 2020-09-16 5 54 04

Run した場合ビルド自体は通るのですが,プレビューを見ようとしたらできないです。
いつか治るといいのですが・・・・(リリース版でもダメ?)

スクリーンショット 2020-09-16 14 13 07

UIKit

私は UIKit の方で対応したので,
このような関数を用意してコールしてみました。
scene に対して present するあたりが特徴でしょうか。

import StoreKit // 追加

@available(iOS 14.0, *)
private func displayOverlay() {
    guard let scene = view.window?.windowScene else { return }
    let config = SKOverlay.AppConfiguration(appIdentifier: self.myAppInfo.appIdentifier, position: .bottom)
    let overlay = SKOverlay(configuration: config)
    overlay.present(in: scene)
}

表示位置は同じで,.bottom.bottomRaised の2パターンです。

SKOverlay_01

ダークモードも対応しているので色の設定等の対応は不要です。

SKOverlay_02

OPEN のところはローカライズされています。(私が英語環境のため)
インストール済みだったら該当のアプリが開きます。

インストールされていない場合やボタン以外のビューをタップすると
App Store に遷移します。

実装していての気づき

シミュレータでは確認できない

実装してみてシミュレータで動作確認しようとしたところ,
アプリ情報が表示されなくて少し時間を無駄にしました。
iOS 14 の実機で確認すればちゃんと表示されます。

SKOverlay_simularor_bottom

スクロール量の調整

表示は画面の下からコンテンツに被さる形となるため,
position を.bottomRaised にすることで事足りる場合はいいのですが,
コンテンツが隠れてしまうことが多いと思います。

UIScrollView(もちろん UITableViewUICollectionViewも)を
使っている場合はスクロール量を少し調整してあげたら良いと思います。
使っていない場合は制約を調整すれば良さそうです。

私のアプリは UIScrollView を使っていたので
下記のように iOS 14 以上ではコンテンツサイズを調整させました。

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    if #available(iOS 14.0, *) {
        self.baseScrollView.contentSize.height += 100.0
        self.displayOverlay()
    }
}

SKOverlay_03

一応表示は下にスワイプすると隠れてくれます。

SKOverlay_04

表示/非表示が完了するタイミング,ロードが失敗した際に呼ばれる,
Delegate メソッドが用意されているので,
レイアウトをしっかりしたい場合等はこちらで調整しても良さそうです(SKOverlayDelegate)。3

例えば,表示されたらコンテンツのサイズ変更し,
非表示になったらアニメーションつけてサイズを戻してあげたりできますね。
お手軽感は薄れますがこっちの方が良さそうです。

// overlay.delegate = self

@available(iOS 14.0, *)
extension HogeViewController: SKOverlayDelegate {
    // SKOverlayの表示が終わったときに呼ばれる
    func storeOverlayDidFinishPresentation(_ overlay: SKOverlay, transitionContext: SKOverlay.TransitionContext) {
        self.baseScrollView.contentSize.height += 100.0
    }

    // SKOverlayが非表示になり始めたときに呼ばれる
    func storeOverlayWillStartDismissal(_ overlay: SKOverlay, transitionContext: SKOverlay.TransitionContext) {
        UIView.animate(withDuration: 1.0) {
            self.baseScrollView.contentSize.height -= 100.0
        }
    }
}

SKOverlay_06

適切なタイミングで非表示に

よく仕様を確認せず表示されたことに満足していたところ,
画面遷移などによって表示させたい画面を離れても
表示されたままになってしまい,UX を損なうことがありました。

VC に対しての present ではなく,scene に対しての present のためです。

/// Attempts to present an app overlay in a `UIWindowScene`.
@available(iOS 14.0, *)
open func present(in scene: UIWindowScene)

そのため,非表示にする実装もしっかりしないといけないです。
画面を離れる際に非表示にするように対応しました。

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if #available(iOS 14.0, *) {
        self.dismissOverlay()
    }
}

@available(iOS 14.0, *)
private func dismissOverlay() {
    guard let scene = view.window?.windowScene else { return }
    SKOverlay.dismiss(in: scene)
}

SKOverlay_05

おわりに

iOS 14 から使えるようになった SKOverlay について書きました。

お手軽に実装できるので,
もし関連アプリやオススメしたいアプリがあったらぜひ実装したいですね。

強調しすぎるのも良くないので適切な画面,タイミングで表示し,
同じく非表示にしてあげるのも大事だと思いました。

ご覧いただきありがとうございました?‍♂️

App Clip でも使えるらしいけどキャッチアップがまだ・・・4

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

Device Orientationで迷ったときに見る記事

向きの一覧

デバイスの向き一覧はこちらの記事にあります。
Orientationのパラメータのまとめ(UIDeviceOrientation, UIInterfaceOrientation)
スクリーンショット 2020-09-17 6.01.56.png

起動してすぐはデバイスの向きが取れない

UIDevice.current.orientationでデバイスの向きは取れるのですが、アプリを起動してすぐは.unKnownを返すようになっています。

起動してすぐの向きはUIInterfaceOrientationで確認できます。

iOS12まで

UIApplication.shared.statusBarOrientation

iOS13以降

self.view.window?.windowScene!.interfaceOrientation

*ただし、画面内のインターフェイスの向きなので、
viewが現れていない状態でUIInterfaceOrientationを確認するとnilになります。
viewDidAppear ~ viewWillDisAppear の間で確認するようにします。
また、デバイスを寝かせて置いた状態(.faceUp, .faceDown)は認識できません。


Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
MLBoysチャンネル
Medium

相棒
note

contact:
rockyshikoku@gmail.com

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