- 投稿日:2020-03-16T22:41:51+09:00
【iOS】SDWebImageで画像のリサイズと角丸を実現
URLから取得した画像を編集して表示したい
LINEやinstagramのDMように、アップロードしたオリジナル画像をリサイズして角丸をつけて表示する、画像送信機能付きのチャット画面のようなものを作りたいことがあります。
手動でもやれると思いますが、URLの画像への変換まで一気通貫でやりたいケースを想定し、ライブラリを使います。いくつかの代表的なものの中から
今回はSDWebImageを使います。特に角丸の実装方法が、SDWebImageの公式ドキュメントからは見つかりにくかったので書いておきます。
前提条件
画像を表示するための基本的なしつらえ(tableViewのレイアウトやdelegateメソッドの用意、imageViewの用意など)は完了している
tranformerを使って画像のリサイズと角丸実装
例えばこんな感じで書きます。
viewController.swiftfunc 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回だけママをやっているスナックに行きました。スナックって味があっていいですね。母の作るハイボールは濃くて美味しかったです。
今回初投稿でしたが、自分が苦しんだものを中心にまた書きたいと思います。
- 投稿日:2020-03-16T20:22:17+09:00
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
こちらの記事から
自分は全ての手順を踏む前に解決できました。強制アンラップを疑ったところ無事直りました!
- 投稿日:2020-03-16T20:18:50+09:00
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
- 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 = viewControllerPlaygroundのシミュレータ上では1/60の0.001666 ... 秒のインターバルにすると56か57あたりになります。60近ければ想定通りなのでまあそんなところでしょう。
ちなみに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あたりです。
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
のようにフラグを立てられているものを回収して描画しているのだと思います。本当の描画回数が分かればもっと興味深い結果になるかもしれません...
- 投稿日:2020-03-16T18:51:46+09:00
NSWindowを最前面に表示する - Keynote, PowerPointのプレゼンモードに対応
概要
- 自分のアプリを最前面に表示するための調査・実装です。
参考
- NSWindow.Level
- NSWindowLevel Enum
- NSWindowLevelの数値定義
- Swift4でのクラス名取得方法まとめ - Qiita
windowNibName
で使用- 他アプリのフルスクリーン上にウインドウを表示する
GitHub
各LevelのWindowを表示する
- 最前面ということで、まず
NSWindowLevel
を考えます。- テストのため、
Swift
で表向きに定義されているwindowLevelでウィンドウをまとめて表示します。PowerPointで最前面
Power Point
やGoogle 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
のみが表示されていますね。実装
- ほぼ他アプリのフルスクリーン上にウインドウを表示するの実装の通りです。
NSPanel
を使うのがミソです。MyWindowController.swiftimport 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.swiftimport 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 } }
- 投稿日:2020-03-16T18:34:44+09:00
Xcode10でAutomatically manage signingだとエラーでる!!
- 投稿日:2020-03-16T15:47:03+09:00
【AppStoreConnect】分かりそうで分からないAppステータスレポート(変更メール)の受け取り方
- 投稿日:2020-03-16T14:09:39+09:00
【XcodeでGithub】XcodeでGithubを使用する方法
「Githubはなんとなくわかるけど、Xcodeで作ったアプリを
Githubにpushするにはどうしたらいいの?」
という方向けに、XcodeならではのGithubの使い方をまとめました!動作環境
Xcode Version 11.3.1
目次
・Xcodeでローカルリポジトリを作成
・(既存のプロジェクトでローカルリポジトリを作成する方法)
・commitする
・Xcodeでリモートリポジトリを作成
・pushする
・テスト用に作ったリモートリポジトリを消したい!Xcodeでローカルリポジトリを作成
②「Create Git repository on my Mac」にチェックして、ローカルリポジトリを作成。
そしてCreate
masterをクリックすると、コミットの履歴が確認できます。
(既存のプロジェクトでローカルリポジトリを作成する方法)
①上部の"Source Control"→"Create Git Repositories..."をクリックして作成
コミットする
まずは、何か編集をしてみてください。
そうすると、下記のように編集ファイルの右にMマークが付いていると思います。
これが、編集されてからまだコミットされていないしるしになります。
次に、そのMマークのついたファイルを右クリック
→"Source Controll"→"Commit "ViewController swift"..."
をクリック。
すると、コミット画面が表示されます。
コメントを加えて右下の"Commit 1 File"をクリック
このように、どこが編集されたのか丸わかりです!!
Xcodeでリモートリポジトリを作成
"Xcode"→"Preferences..."をクリック
すると、このような画面が出るので、GithubのアカウントからSSHを取得して接続してください。上記の接続ができたら、
"Remotes"→"Create "プロジェクト名" Remote..."をクリック
アカウントを設定してCreate
このように、originがあれば無事作成されています!
こちらに作成されていますね!
pushする
すると、履歴が一つ増えていますね。
では、こちらをpushします!
"Source Control"→"Push..."をクリック
無事pushができました!
テスト用に作ったリモートリポジトリを消したい!
テスト用に作ったけど、邪魔だから消したい場合は、
作成したリモートリポジトリをクリック
一番下にある、"Delete this repository"をクリック
※押す前に本当に間違いないか確認してくださいね!!
上記の太字を入力
→今回は"y-aimi/GithubTest"を入力
無事削除されました!
- 投稿日:2020-03-16T11:37:04+09:00
【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ボタンをポチポチ押して行けば、文法的に破綻していないコードをひとまず書くことができてしまいます。しかし、ここら辺の原理原則を正しく理解していれば、予期せぬエラーに頭を悩ませることは少ないですし、コード全体の見通しも良くなりますね。
参考
詳解 Swift 第5版
(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. ↩