- 投稿日:2021-01-22T23:50:34+09:00
[Swift]プロパティについて
はじめに
前回の記事にプロパティのことを説明しました。
しかし、プロパティの分類の仕方がもう一つあるのでそれについて紹介します。プロパティの分類
プロパティは、値を保持する
ストアドプロパティ
と、値を保持しないコンピューテッドプロパティ
があります。今回はその2つについて説明していきます。ストアドプロパティ
以下のtateやyokoのプロパティは、ストアドプロパティです。
struct Area{ var tate=10 var yoko=10 } var A_1=Area()//インスタンス化 var A_2=Area()//インスタンス化 A_1.tate=3 A_2.tate=7 print(A_1.tate)//3 print(A_2.tate)//7ここで、プロパティの値が変更されるとき、変更前と変更後に値を監視する機能があります。
struct Area{ var tate=10{ willSet{ print("old numver=",self.tate) } didSet{ print("new number=",self.tate) } } var yoko=10 } var A_1=Area() A_1.tate=3 //実行結果 //old numver=10 //new number=3willSetの{}では、プロパティの変更前に実行されます。
didSetの{}では、プロパティの変更後に実行されます。
また、このような仕組みをプロパティオブザーバ
と言います。コンピューテッドプロパティ
コンピューテッドプロパティは、プロパティを初期化せずに、なんらかの計算をして値を返すプロパティです。
そして、そのプロパティにアクセスするタイミングで、値を保存することができます。
ほとんど関数みたいなもんですねstruct Area{ var yoko=10 var tate:Int{ get{ return yoko } set{ self.yoko=newValue+2 } } } var A_1=Area() A_1.tate=9 print(A_1.tate)//11プロパティに代入するときはset{}が呼び出されて、A_1.tateみたいに値にアクセスする時(代入する時以外)はget{}が呼び出されます。
A_1.tateでは9を代入しているのでSet{}が呼び出されます。
その時、9にアクセスするにはnewValueを用います。newValue=9です。
それがyokoに代入されるのでyoko=11になります。
最後に,A_1.tateにアクセスしているのでget{return yoko}が実行されます。
yoko=11なので実行結果は11になります。また、
set{}は省略可能で、get{}は必須です。
- 投稿日:2021-01-22T20:47:41+09:00
【Swift】チャットアプリでよくみるキーボードの動きに追随するTextFieldとButtonの実装
チャットアプリでよくみるキーボードの動きに追随するTextFieldとButtoonを実装してみました。
開発環境
Xcode 12.3
Swift 5デモ
こんなやつです。
コード
InputAccesoryViewController.swiftimport UIKit class InputAccesoryViewController: UIViewController { let outputTableView = UITableView() //プロパティにアクセスできるようにグローバル定数として定義 let inputTextField: UITextField = { let textField = UITextField() textField.placeholder = "Aa" textField.backgroundColor = .systemGray6 textField.borderStyle = .roundedRect return textField }() override var canBecomeFirstResponder: Bool { return true } var inputContainerView: UIView! //inputAccesoryViewの中身を作って返り値に入れます。 override var inputAccessoryView: UIView? { if inputContainerView == nil { //容器となるContainerViewを作成 inputContainerView = InputContainerView() inputContainerView.backgroundColor = .systemYellow //ContainerViewに定義しておいたTextFieldを追加 inputContainerView.addSubview(inputTextField) //ContainerViewの高さがキーボードを閉じた時と開いた時で自動でリサイズする inputContainerView.autoresizingMask = .flexibleHeight //送信ボタンの追加 let sendButton = UIButton(type: .system) sendButton.setImage(UIImage(systemName: "paperplane"), for: .normal) //Button Actionを追加 sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside) //ContainerViewにSendButtonを追加 inputContainerView.addSubview(sendButton) //それぞれの部品に制約を設定します sendButton.anchor( right: inputContainerView.trailingAnchor, paddingRight: 8, width: 50, height: 50) inputTextField.anchor( top: inputContainerView.topAnchor, left: inputContainerView.leadingAnchor, bottom: inputContainerView.layoutMarginsGuide.bottomAnchor, right: sendButton.leadingAnchor, paddingTop: 8, paddingLeft: 8, paddingBottom: 8, paddingRight: 8) } //作成したContainerViewを返り値に入れる return inputContainerView } var messages = [String()] override func viewDidLoad() { super.viewDidLoad() inputTextField.delegate = self outputTableView.dataSource = self outputTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") //これでTableViewのスクロールに合わせてキーボードが閉じれるようになる outputTableView.keyboardDismissMode = .interactive view = outputTableView } @objc func didTapSend() { if let message = inputTextField.text, inputTextField.text != "" { messages.append(message) inputTextField.text = "" outputTableView.reloadData() } } } extension InputAccesoryViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { didTapSend() textField.resignFirstResponder() return true } } extension InputAccesoryViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return messages.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell") cell.textLabel?.text = messages[indexPath.row] return cell } }InputContainerView.swift
InputContainerView.swiftimport UIKit class InputContainerView: UIView { //これは、inputAccesoryViewが自動レイアウト制約から適切なサイズになるために必要です override var intrinsicContentSize: CGSize { return CGSize.zero } }UIView Extension
制約を容易にする為にメソッドを作成しました。
UIView+.swiftimport UIKit extension UIView { func anchor(top: NSLayoutYAxisAnchor? = nil, left: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, right: NSLayoutXAxisAnchor? = nil, paddingTop: CGFloat = 0, paddingLeft: CGFloat = 0, paddingBottom: CGFloat = 0, paddingRight: CGFloat = 0, width: CGFloat? = nil, height: CGFloat? = nil) { translatesAutoresizingMaskIntoConstraints = false if let top = top { topAnchor.constraint(equalTo: top, constant: paddingTop).isActive = true } if let left = left { leadingAnchor.constraint(equalTo: left, constant: paddingLeft).isActive = true } if let bottom = bottom { bottomAnchor.constraint(equalTo: bottom, constant: -paddingBottom).isActive = true } if let right = right { trailingAnchor.constraint(equalTo: right, constant: -paddingRight).isActive = true } if let width = width { widthAnchor.constraint(equalToConstant: width).isActive = true } if let height = height { heightAnchor.constraint(equalToConstant: height).isActive = true } } }苦戦したところ
SafeAreaの部分に作成したContainerViewが被って表示され、意図した結果がなかなか出なかった。
textFeild.bottomAnchor
を
inputContainerView.layoutMarginsGuide.bottomAnchor
に指定するのがミソでした。参考
こちらの記事を参考にし、解決することが出来ました。
iPhone X how to handle View Controller inputAccessoryView
何か間違いやより良い方法がありましたら優しく教えていただけると幸いです?♂️
- 投稿日:2021-01-22T20:23:19+09:00
【Swift】timeIntervalSinceが見つからない??
- 投稿日:2021-01-22T19:18:21+09:00
【RxSwift】Reactive の Extension を作成する際にメモリリークが発生した
環境
- Xcode 12.3
- RxSwift 5.1.1
目的
TabTestViewController
のself.viewDidLayoutSubviews()
呼び出しを検知しその際のself.index
を流すObservable<Int>
を作成したいTabTestViewController.swift// こんな感じで利用する currentIndexDidChange を Extension で実装 self.rx.currentIndexDidChange .subscribe(self.indexSubject) .disposed(by: self.disposeBag)当初実装
- 以下のように実装するとメモリリークが発生する
TabTestViewController+rx.swiftextension Reactive where Base: TabTestViewController { var currentIndexDidChange: Observable<Int> { // メモリリークが発生 return sentMessage(#selector(base.viewDidLayoutSubviews)) .map { _ in base.currentIndex } .distinctUntilChanged() .share(replay: 1) } }原因
base
がクロージャ内部で強参照されていることによる間違い:
self
を弱参照しようとした
- 「クロージャの中では
self
を弱参照」と手癖で実装しようとしたところコンパイルエラー
Reactive<Base>
は構造体のためweak
が使えないTabTestViewController+rx.swiftextension Reactive where Base: TabTestViewController { var currentIndexDidChange: Observable<Int> { // 'weak' may only be applied to class and class-bound protocol types, not 'Reactive<Base>' return sentMessage(#selector(base.viewDidLayoutSubviews)) .map { [weak self] _ in guard let self = self else { fatalError() } return self.base.currentIndex } .distinctUntilChanged() .share(replay: 1) } }解決:
base
を弱参照とする
TabTestViewController
はクラスのためweak
で弱参照することができるTabTestViewController+rx.swiftextension Reactive where Base: TabTestViewController { var currentIndexDidChange: Observable<Int> { // viewDidLayoutSubviewsのタイミングでcurrentIndexの変化を検知する return sentMessage(#selector(base.viewDidLayoutSubviews)) .map { [weak base] _ in guard let base = base else { fatalError() } return base.currentIndex } .distinctUntilChanged() .share(replay: 1) } }
- 投稿日:2021-01-22T14:24:50+09:00
コードベースでカスタムTableViewCellを作りたい!
はじめに
「TableViewControllerを配置しただけだと、何か質素なデザインになってしまう。」「セルとセルの空間をもっと空けたい。」こんな時、あると思います。ここでは
UITableViewCell
を継承したクラスを作成し、好みのカスタムセルを作成する方法を共有します。「こうした方がいい!」と感じた場合はコメントで教えて欲しいです。
こんな感じのTableViewができます。(上にNavigationBarがありますが。)作り方
UITableViewCellを継承したクラスを作成する
class CustomTableViewCell: UITableViewCell { }初期化の処理をかく
セルの色や文字の場所などのセットアップを行う関数を行うための場所というイメージです。
class CustomTableViewCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }元々のViewを透明にする処理
ここではカスタムビューを表現する方法として、
UITableViewCell
が元々持っているUIView(contentView
という)とは別のUIViewを用意し、そのUIViewに色をつけるなどしています。なのでcontentView
を透明にし、見えないようにします。class CustomTableViewCell: UITableViewCell { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layoutSubviews() { contentView.backgroundColor = .clear backgroundColor = .clear }//追加した }
contentView
そのものをいじりカスタムセルを作成する方法がありそうですが、技術不足により実装できませんでした。ここら辺のコメント特に募集しています。UIViewを用意し、準備する
ここからはコードベースでレイアウトを組むときにやっていることとあまり変わりません。私の過去の記事でもコードベースでレイアウトを組むことについて書いているので、もし詰まったら参考にして下さい。
private let backView = UIView() private func setupUIView() { backView.backgroundColor = UIColor(255,189,40) backView.layer.cornerRadius = 5 addSubview(backView) backView.snp.makeConstraints { $0.width.equalToSuperview().multipliedBy(0.8) $0.height.equalTo(contentView.snp.height).multipliedBy(0.9) $0.center.equalToSuperview() } }留意点及びやっていることとしては
- レイアウトが楽になるライブラリSnapKitを使っている
- セルの高さが
contentView
の0.9倍になるようにしている- セルの幅が画面サイズの0.8倍になるようにしている
- 角丸をつけている
です。
テキストが表示できるようにUILabelを用意する
セルを表示するだけであれば上述したことだけでOKなのですが、表示させる物が一つは必要なので
UILabel
を追加します。let titleLabel = UILabel() private func setupcellLabel() { titleLabel.font = UIFont.systemFont(ofSize: 20) backView.addSubview(titleLabel) titleLabel.snp.makeConstraints { $0.centerX.equalToSuperview().offset(30) $0.centerY.equalToSuperview() $0.width.equalToSuperview() } }セルから少し右に離れたところにテキストが表示されるようにしています。
セットアップ関数をイニシャライザにいれる
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUIView() setupcellLabel() }ここまでで一応それっぽく動かすための準備はできました。一旦ここまでのソースコードを載せます。
import UIKit class CustomTableViewCell: UITableViewCell { private let backView = UIView() let titleLabel = UILabel() override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUIView() setupcellLabel() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUIView() { backView.backgroundColor = UIColor(255,189,40) backView.layer.cornerRadius = 5 addSubview(backView) backView.snp.makeConstraints { $0.width.equalToSuperview().multipliedBy(0.8) $0.height.equalTo(contentView.snp.height).multipliedBy(0.9) $0.center.equalToSuperview() } } private func setupcellLabel() { titleLabel.font = UIFont.systemFont(ofSize: 20) backView.addSubview(titleLabel) titleLabel.snp.makeConstraints { $0.centerX.equalToSuperview().offset(30) $0.centerY.equalToSuperview() $0.width.equalToSuperview() } } override func layoutSubviews() { contentView.backgroundColor = .clear backgroundColor = .clear } }contentViewのハイライトをなくす
上の通り動かすと、
contentView
がハイライト処理を行ってしまうため、セルを選択したときにしたの画像のようになってしまいます。
これは良くない?
まずはハイライト消します。override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupUIView() setupcellLabel() self.selectionStyle = .none//追加した }次にセルが選択された際に先ほど自分で定義した
UIView
がハイライト処理されるようにします。override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: true) backView.backgroundColor = selected ? UIColor(255,226,146) : UIColor(255,189,40) }
backView
の背景色をセルが選択された時は少し明るめの色、そうでない時は元々の色となるようにします。
三項間演算子を使っていますが、全然if文でも書けます。完成! あとはTabeViewControllerでこのクラスを使うだけです。
TableViewでの処理
UITableViewDataSource
の関数で以下のように書きます。func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell//いつもと違う return cell }あとは
register
のところでregister(CustomTableViewCell.self, forCellReuseIdentifier: "cell")のように書けば先ほど作ったカスタムセルを使うことができます。
- 投稿日:2021-01-22T13:21:25+09:00
【SwiftUI】NavigationViewの遷移のネストでbackボタンが複数生成されていくのにハマったのでメモ
はじめに
SwiftUIを触り始めてまだ1日目
Navigation
での画面遷移を行う際に愚直にコードを書いていたが、解決記事がさっと見つからなかったのでメモ開発環境
- Swift(version 5.3.1)
- Xcode(version 12.2)
解決したいこと
Navigation
のbackボタンが前画面のViewに更新されるのではなく、下に追加されてしまっている…
いやいや、困るよ(汗解決する前
- FirstView.swift
- SecondView.swift
- EndView.swift
以上の3画面で構成している
FirstView.swiftstruct FirstView: View { var body: some View { NavigationView { NavigationLink(destination: SecondView()) { Text("Go Second View") } } .navigationTitle("First View") } }SecondView.swiftstruct SecondView: View { var body: some View { NavigationView { NavigationLink(destination: EndView()) { Text("Go End View") } } .navigationBarTitle("Second View", displayMode: .inline) } }EndView.swiftstruct EndView: View { var body: some View { Text("Hello, World!") } }シンプルに実装したが何が原因なのか…
解決方法
なんか
Navigation
が2個生成されてる雰囲気だよね
FirstView.swift
でNavigationView{}
を記述しているからSecondView.swift
ではもしかしていらない感じ??
NavigationLink
だけ残して動くのかな…SecondView.swiftstruct SecondView: View { var body: some View { //NavigationView {}削除! NavigationLink(destination: EndView()) { Text("Go End View") } .navigationBarTitle("Second View", displayMode: .inline) } }これで実行↓
うんうん、この挙動を求めていたの
無事に解決した〜!最後に
多分、これであってるよね?
愚直に書いてるとこういうところにハマるね
まだまだ初心者だけど頑張ろうQiita初投稿…こんな感じの書き方でいいのかなw
- 投稿日:2021-01-22T12:38:00+09:00
【Model I/O】MDLMeshオブジェクトから頂点データを取得する方法
Model I/Oフレームワークの、MDLMeshオブジェクトから頂点の情報を取得するのが面倒だったので、メモ。
参考:
Read vertex positions with Model I/O
https://gist.github.com/algal/8e31ce035af0aafebc3736661ae9e3cdmdlMeshにMDLMeshオブジェクトの値が入っているとして、これをXcodeのデバッガ(LLDB)上で取得したい場合。
頂点の数を取得する。
デバッガpo mdlMesh.vertexCount // 出力結果:54バッファのデータのstrideを確認する。
デバッガpo mdlMesh.vertexDescriptor.layouts.filter {($0 as! MDLVertexBufferLayout).stride != 0} // 出力結果: // ▿ 1 element // - 0 : <MDLVertexBufferLayout: 0x2824e4490 stride=32>strideは32であることがわかった。
次のコードで、fromByteOffsetにオフセットを与えると頂点の情報が確認できる
n番目の要素を得たいときは、n * strideで取得できる。例えば2番目の要素を取得したいときは2*32を与える・デバッガpo mdlMesh.vertexBuffers.first!.map().bytes.load(fromByteOffset: 1*32, as: Float3.self) // 出力結果: // ▿ SIMD3<Float>(0.5, 0.5, 0.5) // ▿ _storage : SIMD4Storage // - _value : (Opaque Value)2番目の頂点の座標は、x = 0.5, y = 0.5, z = 0.5であることがわかった。
最後に
NoteではiOS開発、とくにCoreML、ARKit、Metalなどについて定期的に発信しています。
https://note.com/tokyoyoshidaTwitterでも発信しています。
https://twitter.com/jugemjugemjugem
- 投稿日:2021-01-22T10:45:59+09:00
【SwiftUI】SwiftUI Appで`window.rootViewController`を用いた初期画面の表示
はじめに
アプリの初期画面を
window.rootViewController
を用いて表示することは採用したアーキテクチャにもよると思いますが、よくあることかと思います。
ただし、プロジェクト作成時にLife Cycle
でSwiftUI App
を選択した場合に少し実装が必要だったため記しておきます。実装
SwiftUI App
でプロジェクトを作成すると、以下のようなファイルが生成されます。
このままだとContentView
が初期表示画面になってしまいます。
例えばCoordinator
やRooter
パターン等を利用したい場合はAppDelegate
やSceneDelegate
を経由したくなります。SampleApp.swiftimport SwiftUI @main struct SampleApp: App { var body: some Scene { WindowGroup { ContentView() } } }まず、
AppDelegate
とSceneDelegate
をそれぞれ自分で作成します。
自作したSceneDelegate
が呼び出されるような実装をAppDelegate
にしていきます。SceneDelegate.swiftimport UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) window.rootViewController = SampleViewController() window.makeKeyAndVisible() self.window = window } } }AppDelegate.swiftimport UIKit class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { let config = UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) config.delegateClass = SceneDelegate.self return config } }これであとは、
AppDelegate
が呼び出されるようにするだけです。
@UIApplicationDelegateAdaptor
を利用します。SampleApp.swiftimport SwiftUI @main struct SampleApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { } } }これで、
SceneDelegate
で指定しているSampleViewController
が表示されるようになります。おわりに
そもそも
SwiftUI App
を選択しなければ良い話ですが、参考になる方がいればいいなと思います。
単純に起動時に処理がしたいだけであればScenePhase
というものもあるようです。
https://developer.apple.com/documentation/swiftui/scenephase
- 投稿日:2021-01-22T07:38:51+09:00
はじめてのSwiftLintで出てきたissuesたち
先日、しどろもどろでSwiftLintを導入してみたら、
ブワァーと出てきました、Warning!!
44個もissues(=問題)があるよ!!
見た瞬間に心が折れたので、一旦閉じて翌日に持ち越してしまってごめんなさい。44個すべてのイシューを確認して、全部修正するのめちゃくちゃ大変そうだなと思ったのですが、
よく見ると同じ文言が繰り返されていることに気づきました。
こう、ぱっと見、英語ばかりで「うえー!」ってなってしまったのは反省です。
よく見れば大したことないことが、わかってきました。文言はたった5つでした。
Line Length Violation
行長違反
Line Length Violation: Line should be 120 characters or less: currently 127 characters
(翻訳)行は120文字以下である必要があります:現在127文字
Trailing Newline violation
末尾の改行違反
Files should have a single trailing newline.
(翻訳)ファイルには、末尾に1つの改行が必要です。
Unused Optional Binding Violation
未使用のオプションのバインディング違反
Prefer
!= nil
overlet _ =
(unused_optional_binding)(翻訳)let _ =
(unused_optional_binding)よりも
!= nil`を優先しますVertical Whitespace violation
垂直空白違反
Limit vertical whitespace to a single empty line.
(翻訳)垂直方向の空白を1行の空行に制限します。
Comment Spacing Violation
コメント間隔違反
Prefer at least one space after slashes for comments.
(翻訳)コメントのスラッシュの後に少なくとも1つのスペースを優先します。所感
いろいろ細かな制約があるんですね。
Human Interface Guidelinesとか見ながら理解を深めていきたいなと思いました。
今回をきっかけに気づいたのですが、
英語には抵抗ない方だと思っていたけど、意外と自覚なく抵抗感あったのだな、と。
iOSエンジニア、Swift触る人になるんだから、
この程度の英語で少しでも拒否反応を示したらだめだなぁっていうのが今回の学びです。今後、また新しいissuesに遭遇したら追記していきます。
- 投稿日:2021-01-22T05:19:00+09:00
~個人メモ~ [swift] 関数・クロージャの基本的な仕様について
※この記事はswift学習中の筆者のノートです。
ちょっと振り返りたい時に、都度本(swift実践入門)を開いて該当ページを探すのがめんどくさかったので簡単に纏めました。
関数
関数とは
"名前を持ったひとまとまりの処理"のことで、外部から処理を呼び出し実行することができる機能のことをいう。
定義方法
関数はfuncキーワードで宣言、そして関数名、引数、戻り値の型、文を定義する
func 関数名(引数名1: 型, 引数名2: 型, ...) -> 戻り値の型 { 関数呼び出し時に実行される文 必要に応じてreturn文で戻り値を返却 }例:数値を2倍する関数
func double(number x: Int) -> Int { return x * 2 } double(number: 150) // 300引数について
仮引数と実引数
- 関数の定義時に宣言するもの・・・
仮引数
- 関数の呼び出し時に指定するもの・・・
実引数
func printInt(_ int: Int) { print(int) } // 関数の定義時に宣言している'int'が仮引数 printInt(1) // 関数の呼び出し時に指定している'1'がが実引数外部引数名と内部引数名
- 外部からの呼び出し時に使用するもの・・・
外部引数名
- 関数内で使用されるもの・・・
内部引数名
func sample(外部引数名 内部引数名: 型) { 関数の内部では内部引数名を使用する } sample(外部引数名: 型)外部引数名は省略することもできる。
その場合は関数の定義時に(_ 内部引数名: )のように外部引数名に_を使用する。
呼び出し時には、:も省略し、値のみの記述で呼び出す。可変長引数
引数定義の末尾に...を加えることでArrey型となる。
(実引数が複数あっても配列として受け取ることができる)func printStrings(strings: String...) { for string in strings { print("\(string)") } } printStrings(strings: "abc", "def", "ghi") // 実行結果 // abc // def // ghiクロージャ
クロージャ(closure)とは
"名前を持たないひとまとまりの処理"のこと。
特徴的なのは名前が不要ということ
、型推論により省略可能範囲が広いということ
、変数に代入でき、クロージャをパラメータとして受け取ることができるということ
などが挙げられる。定義方法
クロージャ式と呼ばれる、処理を{}で囲むフォーマットで
引数・戻り値の型・文
を定義する。{ (引数名1: 型, 引数名2: 型, ...) -> 戻り値の型 in 実行される文 必要に応じてreturn }例:数値を2倍するクロージャ
let double = { (x: Int) -> Int in return x * 2 } double(2) // 4型推論
決まった型にクロージャを代入する場合は、型を省略できる。
// 型を省略しない場合 let double: (Int) -> Int double = { (x: Int) -> Int in return x * 2 } double(2) // 4 // 型推論により省略した場合 let triple: (Int) -> Int triple = { x in return x * 3 } triple(2) // 6実行方法
定義した定数名+()内に引数をいれ実行する。
渡された引数に対して処理が実行される。let lengthString = { (mozi: String) -> Int in return mozi.count } lengthString("文字数を知りたい") // 8ちなみに同じ処理を関数にするとこんな感じ
func lengthString2(mozi2: String) -> Int { return mozi2.count } lengthString2(mozi2: "mozisuuwosiritai") // 16クロージャの引数
関数の引数との違いは以下の通り
- 関数 => 外部引数名と内部引数名がそれぞれ指定できる
- クロージャ => 内部引数名のみ指定できる
すなわちクロージャの場合は()内には値のみ入るということ
また、クロージャでは
デフォルト引数も指定できない
仕様となっている。簡略引数名
クロージャでは簡略引数名を利用し、引数名の定義も省略することが可能である。
簡略引数名は$にインデックス番号をつける。
※型推論できない場合はコンパイルエラーとなる。let array = [1,2,3,4,5] let newArray = array.map { $0 * 5 } // 配列の要素の1番目を $0 と表現している。 // mapメソッドの引数がクロージャ // mapメソッドは要素を一つ一つ取り出して実行するから、取り出された要素は毎回一つ目の要素となる$0 // map( {() -> Int In return $0 * 5 } ) みたいな形が省略前の形と思う newArray // [5, 10, 15, 20, 25]変数と定数のキャプチャ
クロージャ自身が定義されたスコープの変数の値はreturn後も保持される.
let counter: () -> Int do { var count = 0 counter = { count += 1 return count } } counter() // 1 counter() // 2引数としてのクロージャ
クロージャを関数や別のクロージャとして利用する場合の仕様として、
属性
とトレイリングクロージャ
がある属性は2種類
クロージャの型の前に、@属性名を追記して指定する
@escaping
関数に引数として渡されたクロージャが、関数のスコープ外で保持される可能性があることを示す属性
この属性が付与されているかどうかで、コンパイル時にクロージャがキャプチャを行うか判断される
@autoclousure
引数をクロージャで包むことで遅延評価を実現するための属性トレイリングクロージャ
引数のクロージャを外に出すこと。
最後の引数がクロージャの場合のみ可能。func hoge(A: Int, B: Int, Cl: () -> Int { クロージャの処理 })のような関数を呼び出したい場合。
以下、呼び出し時
//通常 hoge(A: 1, B: 5, Cl: { Print(Int) }) //トレイリングクロージャ hoge(A: 1, B: 5) { Print(Int) }と書く事ができる
引数が一つだけの場合、()も省略でき、クロージャを外にだせる。
hoge { clouser }
みたいな形。
けっこうよく見かける形。可読性がよくなる。
- 投稿日:2021-01-22T01:09:52+09:00
英語が苦手な人のためのXcode便利機能 SpellCheck
はじめに
こんにちは!スーパー超絶英弱エンジニアのせーたろです!
profileをplofileと書いてしまう英語力の僕が、インターン先のつよつよエンジニアから教えてもらった便利機能を紹介します。Spell Checkの使い方
Xcode11から導入されたSpell Checkという機能を使います。
Edit < Format < Spelling and GrammarからCheck Spelling While Typingをオンにします。
これでスペルミスをしている箇所に赤い点線が表示されるようになります。
修正
ショートカットキー
command⌘ + ;でスペルミスの箇所がハイライト、
command⌘ + :でSpelling and Grammar のウィンドウが表示されます。
さいごに
この便利機能のおかげで、レビューなどでスペルミスの指摘を減らすことができるのではないでしょうか!
よければ使って見てください!
- 投稿日:2021-01-22T00:07:42+09:00
Swift Packageのバージョンを最新に解決できない
概要
iOSアプリ開発のCI/CDを行うときは、xcode buildやfastlaneでビルドやテストを行うと思います。その際にプロジェクト(ワークスペース)が参照しているSwift Packageがなかなか狙い通りに解決されなかったため、備忘録として残します。
とりあえず結果
結論としてはxcodebuildやfastlaneのパッケージ解決の方法では難しいことがわかりました。Package.resolvedやDerivedDataを削除しても謎のファクターXを参照して、古いリビジョンを解決してしまいます。
最新リビジョンを取得するワークアラウンド
そのプロジェクトがSPMでない場合でも、Package.swiftを用意し、そこにSPMのパッケージを書きます。
で、アップデートして、Package.resolvedを移動させます。// swift-tools-version:5.3 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Hoge", platforms: [ .iOS(.v14), ], dependencies: [ .package(name: "Hoge", url: "https://xxxxxxxxx", .branch("master")) ] )swift package update mv Package.resolved Hoge.xcworkspace/xcshareddata/swiftpm/ bundle exec fastlane testsこの場合、Package.resolvedを参照してほしいので、fastlaneの場合は、disableAutomaticPackageResolutionを有効にします。
Fastfile.run_tests(.., xcargs: "-disableAutomaticPackageResolution", ... )これで最新版をなんとか解決できました!
パッケージ解決に関与してそうなパラメータ
Package.resolved
XXX.xcodeproj/xcshareddata/swiftpm/Package.resolved
というパスにあるファイルです。基本的にこのファイルがある場合は、このファイルに記載されているリビジョンのものがチェックアウトされますが、無視する設定もできるようです。DerivedData
いわゆるビルド時の中間ファイルです。これがあると最新を取得してきてくれないことがあります。 このパスは指定することができるようで、fastlaneだと
cloned_source_packages_path
というパラメータで指定することができます。xcodebuildだとclonedSourcePackagesDirPath
というパラメータです。resolvePackageDependencies
xcodebuild
コマンドに渡すパラメータです。このパラメータの挙動を正確に把握していないのですが、プロジェクトAがSwiftPackageBとSwiftPackageCをxcodeproj
で参照しており、SwiftPackageBがSwiftPackageCを参照していた場合、プロジェクトAが参照するプロジェクトCのバージョンは、プロジェクトBのPackage.Swiftのバージョンになる、という挙動をしました。というところまでは調べたのですが、ファクターXがなんなのかが結局わかりませんでした。