20200316のSwiftに関する記事は8件です。

【iOS】SDWebImageで画像のリサイズと角丸を実現

URLから取得した画像を編集して表示したい

LINEやinstagramのDMように、アップロードしたオリジナル画像をリサイズして角丸をつけて表示する、画像送信機能付きのチャット画面のようなものを作りたいことがあります。

手動でもやれると思いますが、URLの画像への変換まで一気通貫でやりたいケースを想定し、ライブラリを使います。いくつかの代表的なものの中から
今回はSDWebImageを使います。

特に角丸の実装方法が、SDWebImageの公式ドキュメントからは見つかりにくかったので書いておきます。

前提条件

画像を表示するための基本的なしつらえ(tableViewのレイアウトやdelegateメソッドの用意、imageViewの用意など)は完了している

tranformerを使って画像のリサイズと角丸実装

例えばこんな感じで書きます。

viewController.swift
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

let cell = // cellの生成は省略。imageViewが乗っかったcellを想定。

let url = // 省略。固定URLや、データベースなどから取得したURLを変数に代入。

// 画像のリサイズ。任意のサイズを設定。
let sizeTransformer = SDImageResizingTransformer(size: CGSize(width: 200, height: 200),scaleMode: .aspectFill)

// 角丸をつける。任意の数値や色を設定。
let roundCornerTransformer = SDImageRoundCornerTransformer(radius: 18.5, corners: .allCorners, borderWidth: 2.0, borderColor: UIColor.clear)

// 上記2つのtransformerをまとめて1つにする
let pipeLineTransformer = SDImagePipelineTransformer(transformers: [sizeTransformer, roundCornerTransformer])

cell.imageView.sd_setImage(with: url, placeholderImage: nil, context: [.imageTransformer: pipeLineTransformer])

}

公式の読み込みも大変だと思うのでご参考になれば幸いです。

最近の話

この前母親が週に2回だけママをやっているスナックに行きました。スナックって味があっていいですね。母の作るハイボールは濃くて美味しかったです。

今回初投稿でしたが、自分が苦しんだものを中心にまた書きたいと思います。

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

iosアプリリリースまでにお世話になった記事

Archivingする前に

長時間待った挙句容量不足でできません!ってなりました
https://qiita.com/y-some/items/27cecfc5c7ed5725587c
前もってしておくことをお勧めします!

sudo rm -rf ~/Library/Developer/Xcode/iOS DeviceSupport/
sudo rm -rg ~/Library/Developer/Xcode/DerivedData

これだけでもだいぶましになると思います

Provisioning profile "XXXXXX" doesn't include signing certificate YYYY って出る

https://qiita.com/warapuri/items/2a32cb2201ce75aa5f4b#4-%E5%86%8D%E8%B5%B7%E5%8B%95

simulatorだと問題ないのに実機だと落ちる

https://qiita.com/edo_m18/items/dbf720b1964f7cd5e9b9

設定アプリ > プライバシー > 解析 > 解析データ

これで自分の開発したアプリ名のログをみてみましょう。
自分の場合は

thread0 crashed with arm thread state

というのを発見して

https://stackoverflow.com/questions/54427407/thread-0-crashed-with-arm-thread-state-64-bit
こちらの記事から 
自分は全ての手順を踏む前に解決できました。強制アンラップを疑ったところ無事直りました!

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

SwiftUIのSubViewは画面更新ごとに生成と破壊を繰り返す

この記事の目的

SwiftUIのSubViewはその親Viewを更新されるタイミングで繰り返し生成と破棄されます。画面を更新されるたびにstructであるSubViewは破棄されて作り変えられているわけです。これを数字で理解するのがこの記事の目的です。

SubViewは描画が必要なタイミングで生まれ変わっている、というのを言葉ではなく数字で分かりたいわけです。

具体的な前提

前提を説明すると、とあるContentViewが@ObservedObjectを持つ場合にその@Publishedなプロパティが更新された場合、ContentViewは更新のためにvar body: some View {}プロパティが呼び出され、そこに記述されているSubViewはその都度生成されているわけです(もちろんこれは@Stateもしくは@Bindingが更新された場合にも同じです)。

上記SwiftUIのレンダリングシステムについてはBOOTHで電子書籍として販売している内容にその情報源を詳しく書いています。

SwiftUIガイドブック - レンダリングシステムの考察とデータの使い分け
https://booth.pm/ja/items/1829015

ここまでが前提の確認です。

記事の目的のための本題

このSwiftUIのSubViewの更新はどこまでのスピードまで耐えられるのかというのが本題です。例えば@Publishedなプロパティの更新時間が例えば0.0001秒間隔ならその間隔に併せてViewも更新するのでしょうか。しないでしょ。無駄です。なぜならそんな間隔で画面を更新されても目で見て分からないからです。

この予想としては60fpsもしくは120fpsだろうと考えます。この数字は2019年までに発売されたiPhoneが60fpsで、iPad Proが120fpsだからです。

60fpsの場合、つまり1秒間あたり60回画面を更新しているなら、1回の画面更新秒数は 1 / 60 で 0.001666 ... 秒となります。そのためこれを検証するのに 0.001666 ... の更新タイミングで@ObservedObject@Publishedプロパティを書き換え、1秒間に60回SubViewがinitメソッドを呼んでいるということが分かれば、つまり60fpsで画面更新できるということが確認できます。

  • 0.001666 ... 秒ごとに@ObservedObject@Publishedプロパティを書き換える
    • SubViewが更新される
      • SubViewのinit時の回数を数える
        • 1秒後 にinit回数が60であればインターバル0.001666 ... 秒でViewは作り変えられている

もちろん1秒後よりも10秒後にするほうが精度的には良いと思います。が、精度はあんまり求めていないので今回は1秒にしています。

さらにインターバルを小さくしていき、1秒間に最大何回initが実行されているか分かれば面白いところです。

実験

まずはPlaygroundで試す

  • Xcdoe 11.4 beta
  • Playground

スクリーンショット 2020-03-16 19.43.17.png

  • SubViewを青い背景としてTextでinitの回数を表示
    • init回数は何度かやると56か57になる
import SwiftUI
import UIKit
import PlaygroundSupport

// SubViewがinitされるたびにそのタイミングを保持
var dates = [Date]()

struct ContentView: View {
    @ObservedObject var myTimer = MyTimer()

    var body: some View {
        VStack(alignment: .center)  {
            Text("interval: \(myTimer.interval.description)")
            Text("limit: \(myTimer.limit)")

            SubView(myTimer: self.myTimer)
                .background(Color.blue)
                .foregroundColor(Color.white)
        }
    }
}

struct SubView: View {
    @ObservedObject var myTimer: MyTimer
    init(myTimer: MyTimer) {
        self.myTimer = myTimer
        dates.append(Date())
    }

    var body: some View {
        VStack {
            Text("SubView: init count \(dates.count)")
        }
    }
}

class MyTimer: ObservableObject {
    // これを更新するとViewがreloadされる
    @Published var text: String = ""
    // タイマーを更新する間隔
    let interval: TimeInterval = (1 / 60) // 0.0166...
    // タイマーを止めるlimit時間
    let limit = 1.0

    private lazy var timer: Timer = {
        Timer.scheduledTimer(
            withTimeInterval: self.interval,
            repeats: true
        ) { timer in
            self.text = timer.fireDate.description
        }
    }()

    init() {
        timer.fire()

        Timer.scheduledTimer(
            withTimeInterval: limit,
            repeats: false
        ) { _ in
            self.timer.invalidate()
        }
    }
}

let viewController = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = viewController

Playgroundのシミュレータ上では1/60の0.001666 ... 秒のインターバルにすると56か57あたりになります。60近ければ想定通りなのでまあそんなところでしょう。

スクリーンショット 2020-03-16 19.43.12.png

ちなみに1秒間あたりのinit上限を探っていったところ、インターバルを240(0.0041秒間隔)より細かくすることはできませんでした。OS的にはゲーミングディスプレイの240fpsを想定しているのでしょう。Macbookのディスプレイで240fpsなんて描画してるわけないですが、1秒間にSwiftUIのinitは240回までできます

実機でやってみる

PlaygroundからプロジェクトをSwiftUI用の[Single View App]に作り直し、iPhone 11Pro実機でインターバルを240(0.0041秒間隔)で試すと、だいたい115~125あたりです。

IMG_1708.PNG

iPhone 11 Proのディスプレイは120Hzで駆動すると書かれた公式仕様がないため、60fpsがハードウェア的な上限だとは思いますが、システム的な上限は120に近い値が出せるということがわかります。

結論

やはりViewは@ObservedObject@Publishedなプロパティが更新されるとSubViewは再生と破壊を繰り返す。

そしてリミッターが存在しそうでありそれはシミュレータと端末では違いがある。

  • Playground(というかシミュレータでは)
    • 1秒間に60回近くプロパティを更新すればViewはその都度initされている
    • インターバルは240回の更新が限界
      • (もちろん画面更新が1秒間に240回やってるとは思えない)
      • 240はゲーミングディスプレイの更新周波数と同じでキリが良すぎる...
  • 実機のiPhone 11 Pro
    • インターバルは120回付近の更新が限界
      • (もちろん実機画面更新が1秒間に140回やってるとは思えない)

念の為書いておくこと

SwiftUIのレンダリングシステムが描画をサボっているかどうかはわからない

Viewが0.001666 ... 秒ごとに作り変えられていたとしても、本当に画面が更新されているのかSwiftUIのレンダリングシステムによってスキップされているかはわかりません。わかるのはinitの回数だけです。initが動作するとbodyが更新されますが、処理的に重かったりすると次のinitタイミングが動作してしまい前のbody更新する前に次の処理が入る、とかもあるでしょう。今回はそんなに重い処理ではないのでそうならないとは思いますが。

おそらく基本的にはOSの描画タイミングに合わせて画面を更新しており、その時間までにsetNeedsDisplayのようにフラグを立てられているものを回収して描画しているのだと思います。

本当の描画回数が分かればもっと興味深い結果になるかもしれません...

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

NSWindowを最前面に表示する - Keynote, PowerPointのプレゼンモードに対応

概要

  • 自分のアプリを最前面に表示するための調査・実装です。

参考

GitHub

Qiita_NSWIndowLevelSample

各LevelのWindowを表示する

  • 最前面ということで、まずNSWindowLevelを考えます。
  • テストのため、Swiftで表向きに定義されているwindowLevelでウィンドウをまとめて表示します。

-w628

PowerPointで最前面

  • Power PointGoogle Slideのプレゼンテーションモードでは、WindowdowLevelはそのままでフルスクリーンとなっています。
  • NSPanelに対して、下記の通り設定を行います。
  • またNSWindowLevel.normalよりも大きければOKですね。
// フルスクリーン時に表示するための設定
myWindowController.myWindow.styleMask.insert(.nonactivatingPanel)
myWindowController.myWindow.collectionBehavior.insert(.canJoinAllSpaces)
myWindowController.myWindow.collectionBehavior.insert(.fullScreenAuxiliary)

Keynoteで最前面

  • 一方Keynoteのプレゼンテーションモードで最前面表示をするには、WindowLevel.popUpMenu以上にする必要があります。
    • PowerPointと目的は同じですが手段が違うということですね。
  • 下記が実際に試してみた所
    • .popUpMenu.screenSaverのみが表示されていますね。

実装

MyWindowController.swift
import Cocoa

class MyWindowController: NSWindowController {

    @IBOutlet weak var myWindow: NSPanel!
    @IBOutlet weak var windowLevelLabel: NSTextField!

    override var windowNibName : String? {
        return String(describing: type(of: self))
    }

    override func windowDidLoad() {
        super.windowDidLoad()

        // フルスクリーン時に表示するための設定
        self.myWindow.styleMask.insert(.nonactivatingPanel)
        self.myWindow.collectionBehavior.insert(.canJoinAllSpaces)
        self.myWindow.collectionBehavior.insert(.fullScreenAuxiliary)
    }
}

AppDelegate.swift
import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var window: NSWindow!
    var myWindowControllerList = [MyWindowController]()

    struct WindowControllerPropertyKey {
        static let nameKey        = "name"
        static let windowLevelKey = "windowLevel"
    }

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application

        var windowSettings:[Dictionary<String, Any>] = []

        // [NSWindow\.Level](https://developer.apple.com/documentation/appkit/nswindow/level)
        // [NSWindowLevel Enum](https://docs.microsoft.com/en-us/dotnet/api/appkit.nswindowlevel?view=xamarin-mac-sdk-14)
        // 上表の通り、数字の順で間違い無さそう。下に行くほど最前面
//        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".normal",
//                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.normal])

        // 以下3つは同じ優先度
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".submenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.submenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".tornOffMenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.tornOffMenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".floating",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.floating])

        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".modalPanel",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.modalPanel])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".mainMenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.mainMenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".statusBar",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.statusBar])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".popUpMenu",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.popUpMenu])
        windowSettings.append([WindowControllerPropertyKey.nameKey        : ".screenSaver",
                               WindowControllerPropertyKey.windowLevelKey : NSWindow.Level.screenSaver])

        for windowSetting:Dictionary<String, Any> in windowSettings {
            let myWindowController = MyWindowController()

            myWindowController.showWindow(self)
            myWindowController.myWindow.title               = windowSetting[WindowControllerPropertyKey.nameKey] as! String
            myWindowController.windowLevelLabel.stringValue = windowSetting[WindowControllerPropertyKey.nameKey] as! String
            myWindowController.myWindow.level               = windowSetting[WindowControllerPropertyKey.windowLevelKey] as! NSWindow.Level

            myWindowControllerList.append(myWindowController)
        }
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }


}

補足

NSColorPanelをKeynoteのプレゼンモードで表示する

概要

  • NSColorPanelのWindowLevelを一旦設定しても、再度NSColorWellを押下して表示した際に、.floatingに戻ってしまっています(なぜだろう?)
  • そこでorder(_:relativeTo:)で表示前にアクションを挟み込むことで対応しました。

参考

実装

import Cocoa

extension NSColorPanel {    
    // [How to get notified when NSWindow opens?](https://stackoverflow.com/questions/20453965/how-to-get-notified-when-nswindow-opens)
    open override func order(_ place: NSWindow.OrderingMode, relativeTo otherWin: Int) {
        super.order(place, relativeTo: otherWin)
        NSColorPanel.shared.level = .screenSaver
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Xcode10でAutomatically manage signingだとエラーでる!!

Xcode9にて開発したアプリで、Xcode10で下記の様なエラーがでる場合の対処。
Code signing is required for product type 'Application' in SDK 'iOS 12.0'
Automatically manage signingのチェック外し、下記のような設定とする。
スクリーンショット 2020-03-16 18.28.51.png

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

【AppStoreConnect】分かりそうで分からないAppステータスレポート(変更メール)の受け取り方

概要

App Store上のアプリのステータスが変更された時にSlackと連携したいなと思った時に
そもそもメールが来てないことに気付きました。
このメールが来る様にしたいんだけど、実際にどこいじったら来るのか分からなかった。

備考

変更するにはもしかしたらApp Store Connect上である程度の権限が必要かもしれません。

設定方法

「ユーザとアクセス」をクリック

スクリーンショット_2020-03-16_15_35_36.png

自分のセルをクリック

スクリーンショット_2020-03-16_15_36_01.png

「App ステータスレポート」の選択肢を

スクリーンショット_2020-03-16_15_36_15.png

「はい」に変更

スクリーンショット 2020-03-16 15.36.25.png

誰かのお役に立てば。

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

【XcodeでGithub】XcodeでGithubを使用する方法

「Githubはなんとなくわかるけど、Xcodeで作ったアプリを
 Githubにpushするにはどうしたらいいの?」
という方向けに、XcodeならではのGithubの使い方をまとめました!

動作環境

Xcode Version 11.3.1

目次

・Xcodeでローカルリポジトリを作成
・(既存のプロジェクトでローカルリポジトリを作成する方法)
・commitする
・Xcodeでリモートリポジトリを作成
・pushする
・テスト用に作ったリモートリポジトリを消したい!

Xcodeでローカルリポジトリを作成

①プロジェクトを作成
image.png

image.png

image.png

②「Create Git repository on my Mac」にチェックして、ローカルリポジトリを作成。
そしてCreate
image.png

③ローカルリポジトリができているかチェック
image.png

masterをクリックすると、コミットの履歴が確認できます。
image.png

(既存のプロジェクトでローカルリポジトリを作成する方法)

①上部の"Source Control"→"Create Git Repositories..."をクリックして作成
image.png

コミットする

まずは、何か編集をしてみてください。
そうすると、下記のように編集ファイルの右にMマークが付いていると思います。
これが、編集されてからまだコミットされていないしるしになります。
image.png

次に、そのMマークのついたファイルを右クリック
→"Source Controll"→"Commit "ViewController swift"..."
をクリック。
image.png

すると、コミット画面が表示されます。
コメントを加えて右下の"Commit 1 File"をクリック
image.png

確認してみましょう!
image.png

このコミットをダブルクリックすると、
image.png

このように、どこが編集されたのか丸わかりです!!

Xcodeでリモートリポジトリを作成

"Xcode"→"Preferences..."をクリック

image.png

image.png
すると、このような画面が出るので、GithubのアカウントからSSHを取得して接続してください。

上記の接続ができたら、
"Remotes"→"Create "プロジェクト名" Remote..."をクリック
image.png

image.png

アカウントを設定してCreate

image.png

このように、originがあれば無事作成されています!

実際にGithubを見て、確認してみましょう
image.png

こちらに作成されていますね!

pushする

それでは、またファイルを編集してみましょう。
image.png

image.png

image.png

すると、履歴が一つ増えていますね。

では、こちらをpushします!
"Source Control"→"Push..."をクリック
image.png

"Push"をクリック
image.png

image.png

無事pushができました!

テスト用に作ったリモートリポジトリを消したい!

テスト用に作ったけど、邪魔だから消したい場合は、
作成したリモートリポジトリをクリック
image.png

"Setting"をクリック
image.png

一番下にある、"Delete this repository"をクリック
※押す前に本当に間違いないか確認してくださいね!!
image.png

上記の太字を入力
→今回は"y-aimi/GithubTest"を入力
image.png

Githubのパスワードを入力
image.png

image.png

無事削除されました!

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

【Swift】Swiftの値型と参照型、はじめからていねいに

Swiftのデータ型

Swiftではクラスのイニシャライザによって生成されたクラスの実体の他に、整数や構造体などの他のデータ型も全てインスタンスと称します。そして、インスタンスの型には値型と参照型の2種類があります。

値型

値型 (value type)とは代入や関数に引数として渡される際に値がコピーされるデータ型です。代入や演算によって前のデータは変更されません。Swiftにおいてはほとんど全て(クラスとクロージャ以外)のデータ型が値型です。1

var a = 0 //整数(Int型)は値型のデータ
var b = a //aの値をコピーしてbに代入

b = 1

print("a:\(a)")
print("b:\(b)")

---
a:0 //b = 1 によって変更は受けない
b:1

参照型

参照型 (refrence type)とは代入や関数に引数として渡される際に値そのものではなく、データ自体への参照をを渡すデータ型です。ここでの参照とはデータ自体が保存されているメモリ上の領域のアドレスのことを指します。代入や関数に渡された後の演算操作は渡された参照をもとに辿って代入した側のインスタンスに対して行われます。よって、代入した側もそれらの操作による変更を受けます。Swiftにおいてはクラスとクロージャが参照型のデータ型です。

//クラス定義(クラスは参照型のデータ)
class Artist {
    var genre: String

    init(genre: String) {
        self.genre = genre
    }
}

var a = Artist(genre: "rock") //genreをrockとして生成
var b = a //aの参照(メモリ上のアドレス)をコピーして代入

b.genre = "pop" //b(=aから渡された参照から辿ることのできるa自体)のgenreを書き換える

print("a:\(a.genre)")
print("b:\(b.genre)") //b(=aから渡された参照から辿ることのできるa自体)のgenreを出力する

---
a:pop
b:pop

データ型とlet

上記の内容は最初は混乱しやすい部分でもありますが、値型と参照型の2種類があるということが事前に分かっていれば、理解するのに難しくない挙動だと思います。しかし、同じlet宣言でも値型のデータ型に対するlet宣言とデータ型に対するlet宣言では意味合いが少し異なります。以下では値型の構造体 (struct)と参照型のクラスを使って、両者の挙動の違いをまとめます。

値型の場合

値型のデータ型でlet宣言を行なった場合、その定数に保存された値自体が書き換えられないようになります。

以下の例では、構造体Hoge型のインスタンス hogeをletで宣言したのちにプロパティaを書き換えようとしますが、これは値型の定数の値を書き換えようとしているためエラーになります。

//構造体定義
struct Hoge {
    var a: UInt8
    var b: UInt8
}


let hoge = Hoge(a: 100, b: 200) //インスタンス生成
foo.a = 5 // Cannot assign to property: 'hoge' is a 'let' constant

参照型の場合

一方、参照型の場合、データ自体にはインスタンスに対する参照が格納されるため、参照型のデータ型に対するlet宣言は参照の書き換えに対して効力があります。すなわち、参照自体の書き換えができないものの、クラスが持つプロパティの書き換えに対して効力があるわけではありません。したがって、あるクラスのインスタンスがlet宣言で生成されても、そのインスタンスが保持するプロパティの書き換えをすることができます。以下の例をみてください。

//クラス定義
class Fuga {
    var a: UInt8
    var b: UInt8

    init(a: UInt8, b: UInt8) {
        self.a = a
        self.b = b
    }
}

let fuga = Fuga(a: 100, b: 200) //インスタンス生成
fuga.a = 5 //書き換えができる

終わりに

iOSアプリの開発環境であるXcodeはエラー補完の機能が充実しているので、ここら辺の内容をあまり理解していなくてもfixボタンをポチポチ押して行けば、文法的に破綻していないコードをひとまず書くことができてしまいます。しかし、ここら辺の原理原則を正しく理解していれば、予期せぬエラーに頭を悩ませることは少ないですし、コード全体の見通しも良くなりますね。

参考


  1. (3/16追記)厳密には列挙体(enum)と構造体(struct)が値型として定義されていて、内部的にstructを基に実装されている基礎的なデータ型が値型になるため、ほとんどの型が値型ということになります。 > You’ve actually been using value types extensively throughout the previous chapters. In fact, all of the basic types in Swift—integers, floating-point numbers, Booleans, strings, arrays and dictionaries—are value types, and are implemented as structures behind the scenes. 

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