- 投稿日:2020-11-15T23:14:47+09:00
swift実践入門output Chapter5 後編
制御構文
条件分岐、繰り返し、遅延実行、パターンマッチ
プログラムの実行フローを制御する構文。制御フローの制御には条件分岐や繰り返しなどがあり、それを組み合わせ実行し自在に操るfor-in文 シーケンスの要素の列挙
chapter5.swiftlet array = [1,2,3] for element in array { print(element) }Dictionary
chapter5.swiftlet dictionary = ["a": 1, "b": 2] for (key,value) in dictionary { print("Key: \(key), vlue: \(value)") }while文 継続条件による繰り返
chapter5.swiftvar f = 1 while f < 4 { print(f) f += 1 }repeat while文
while文は実行前に条件式を評価するため、場合によっては一度も実行されない
そこで条件式の成否にかかわらず、必ず1回以上繰り返しを実行したい場合は、repear whieを使用chapter5.swiftvar t = 1 repeat { print(t) t += 3 } while t < 1実行中の中断
break文は繰り返し文全体を終了させます。
chapter5.swiftvar containsTwo = false let garray = [1,2,3] for element in garray { if element == 2 { containsTwo = true break } print("element:\(element)") } print("containsTwo:\(containsTwo)")continue文は現在の処理のみを中断し、後続の処理を継続する
chapter5.swiftvar odds = [Int]() let rarray = [1,2,3,4,5,6] for element in rarray { if element % 2 == 1 { odds.append(element) continue } print("even:\(element)") } print("odds:\(odds)") }遅延実行 のためのdefer文
defer文 スコープ退出時の処理
chapter5.swiftvar count = 0 func someFUNTIONS() -> Int{ defer { count += 1 } return count } print(someFUNTIONS()) print(someFUNTIONS()) print(count)パターンマッチ 値の構造や性質による評価
swiftには値の持つ構造や性質を表現するパターンという概念がある
パターン待ちとは、値が特定のパターンに合致するかの検査をすること式パターン ~ = 演算子による評価
chapter5.swiftlet integer = 6 switch integer { case 6: print("match: 6") case 5...10: print("match: 5...10") default: print("default") }バリューバインディング 値の代入を伴う評価
chapter5.swiftlet values = 3 switch values { case let matchValue : print(matchValue) }オプショナルパターン Optional型の値の有無の評価
chapter5.swiftlet values = 3 let optionalD = Optional(4) switch optionalD{ case let f?: print(f) default: print("nil") } }列挙型ケースパターン ケースとの一致の評価
chapter5.swiftenum Hemisphere { case northern case southern } let hemisphere = Hemisphere.southern //let hemisphere = Optional(Hemisphere.southern) switch hemisphere { case .northern: print("match: .northern") case .southern: print("match: .southern") //case nil: // print("nil") } enum Color { case rgb(Int,Int,Int) case cmyk(Int,Int,Int,Int) } let color = Color.rgb(100, 200, 230) switch color { case .rgb(let r, let h, let j): print(".rgb: (\(r), \(h), \(j))") case .cmyk(let k,let y, let u, let o): print(" .cmyk: (\(k),\(y),\(u),\(o))") }is演算子による型キャスティングパターン 型の判定による評価
chapter5.swiftlet any: Any = "fafafa" switch any { case is String: print("match: String") case is Int: print("match: Int") default: print("default") } }as演算子による型キャスティングパターン 型のキャストによる評価
chapter5.swiftlet anys : Any = "1" switch anys { case let string as String: print("match: string(\(string))") case let int as Int: print("match: int(\(int))") default: print("default") }パターンマッチが使える場所if 文
chapter5.swiftlet fff = 9 if case 1...10 = fff { print("yes") } //guard 文 func someFunc() { let value = 9 guard case 1...10 = value else { return } print("yes") } someFunc() //for in文 let harray = [1,2,3,4,5,6] for case 2...5 in harray { print("2以上5以下") }while 文
chapter5.swiftvar nextValue = Optional(1) while case let value? = nextValue { print("value:\(value)") if value >= 8 { nextValue = nil } else { nextValue = value + 1 } }
- 投稿日:2020-11-15T22:43:05+09:00
swift実践入門output Chapter5
制御構文
条件分岐、繰り返し、遅延実行、パターンマッチ
プログラムの実行フローを制御する構文。制御フローの制御には条件分岐や繰り返しなどがあり、それを組み合わせ実行し自在に操るif 文 条件の成否による分岐
chapter5.swiftlet value = 5 if value <= 4 { print("valueは4以下") }else { print("valueは5以上") }if let文 値の有無による分岐
オプショナル型とは変数にnilの代入を許容するデータ型で、反対に非オプショナル型はnilを代入できません。オプショナル型の変数にはデータ型の最後に「?」か「!」をつけます。
chapter5.swiftlet optionalA = Optional(1) if let a = optionalA { print("値は\(a)です") }else { print("値は存在しません") } let optionalB = Optional("b") let optionalC = Optional("c") if let b = optionalB, let c = optionalC{ print("値は\(b)と\(c)ですよ") }else { print("ない") }guardl文 条件不成立時に早期退出する分岐
guard文は、条件を満たさない場合の処理を記述する構文です。
条件を満たさない場合には、メソッドを終了したり、繰り返し処理を終了したり、スコープを抜けるための処理を書く必要がある。guard文を使って条件を満たさない場合に処理を抜けるコード
chapter5.swift//guard 条件式 else { // 条件式がfalseの場合実行される文 // guard文が記述されているスコープ文に退出する必要がある //} func someFunction() { let value = -1 guard value >= 0 else { print("0未満の値") return } print(value) } someFunction()guard文のスコープ外への退出の強制
chapter5.swiftfunc printIfPositive(_ a: Int) { guard a > 0 else { return } print(a) } printIfPositive(6)guard文で宣言された変数や定数へのアクセス
guard let文とif letぶんの違いは
guard let文で宣言された変数や定数は guard let文以降で利用可能という点chapter5.swiftfunc someFONCTION() { let a :Any = 3 guard let int = a as? Int else { print("aはInt型ではありません") return } print("値はInt型の\(int)です") } someFONCTION() func checkInt(num:Int) { guard num >= 0 else { print("マイナスです") return } print("プラスです") } checkInt(num:-10) checkInt(num:10)switch文 複数のパターンマッチによる分岐
chapter5.swiftlet r = -1 switch r { case Int.min ..< 0: print("rは負の数") case 1 ..< Int.max: print("rは正の数") default: print("aは0") }ケースの網羅性のチェック
chapter5.swiftenum SomeEnum { case foo case bar case baz } let foo = SomeEnum.foo switch foo { case .foo: print(".foo") case .bar: print(".bar") case .baz: print(".baz") }whereキーワード ケースにマッチする条件の追加
chapter5.swiftlet optionalP: Int? = 12 switch optionalP { case .some(let a) where a > 10: print("10より大きい値\(a)が存在します") default: print("値が存在しない、もしくは、10以下") } let g = 1 switch g { case 1: print("実行される") break print("実行されない") default: break }
- 投稿日:2020-11-15T22:02:41+09:00
TableViewでのデータの読込・リフレッシュ・更新についての処理
はじめに
Qiitaが提供しているAPIを使用して、
TableView
に記事を表示する際に、データの読み込み・リフレッシュ・更新について実装しましたので備忘録として投稿します。概要
QiitaAPI
の初期取得- 取得している
QiitaAPI
のリフレッシュTableView
下部までスクロールした際にQiitaAPI
追加取得※上記にフォーカスをあたて説明になります。
動作環境
【Xcode】Version 12.0.1
【Swift】Version 5.3実装コード
CustomTableView.swiftimport UIKit class CustomTableView: UITableView { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } override init(frame:CGRect, style: UITableView.Style) { super.init(frame: frame, style: .plain) setup() } private func setup() { let refreshControl = UIRefreshControl() refreshControl.attributedTitle = NSAttributedString(string: "読み込み中") self.refreshControl = refreshControl // UITableViewが空のときの線(セパレーター)を消すために、空のViewを設定 self.tableFooterView = UIView() } func addTargetToRefreshControl(_ target: Any?, action: Selector, event: UIControl.Event) { self.refreshControl?.addTarget(target, action: action, for: event) } func beginRefreshing() { guard let refreshControl = self.refreshControl else { return } refreshControl.beginRefreshing() refreshControl.sendActions(for: .valueChanged) self.contentOffset.y = -self.bounds.height } func endRefreshing() { guard let refreshControl = self.refreshControl else { return } refreshControl.endRefreshing() } }ViewController.swiftclass ViewController: UIViewController { @IBOutlet private weak var tableView: CustomTableView! private var reloading: Bool = false private var page: Int = 20 # ・・・省略・・・ override func viewDidLoad() { super.viewDidLoad() # ・・・省略・・・ tableView.addTargetToRefreshControl(self, action: #selector(self.refreshArticlesAction), event: .valueChanged) // refreshControlを呼び出し、ローディングする tableView.beginRefreshing() # ・・・省略 (Qiitaからデータ取得する処理) ・・・ tableView.endRefreshing() tableView.reloadData() } @objc private func refreshArticlesAction() { // 取得数を初期値にする page = 20 # ・・・省略 (Qiitaからデータ取得する処理) ・・・ tableView.endRefreshing() tableView.reloadData() } } # ・・・省略・・・ extension ViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { let height = scrollView.frame.size.height let contentYoffset = scrollView.contentOffset.y let distanceFromBottom = scrollView.contentSize.height - contentYoffset if distanceFromBottom < height { // 何回も呼ばれてしまうので、リロード中は変数:reloadingをtrueにしておく if !reloading && page < 100 { // 取得数を20追加にする if page <= 100 { page += 20 } // リロード処理のフラグ変更を「true」 reloading.toggle() # ・・・省略 (Qiitaからデータ取得する処理) ・・・ // リロード処理のフラグ変更を「false」 reloading.toggle() tableView.reloadData() } } } }実装詳細
1.
QiitaAPI
の初期取得
viewDidLoad
でrefreshControl.beginRefreshing()
を呼び出してローディング開始。Qiita
からデータ取得。refreshControl.endRefreshing()
でローディング停止し、tableView.reloadData()
で内容更新します。2. 取得している
QiitaAPI
のリフレッシュ
UIRefreshControl
のインスタンスにaddTarget
で指定しています。CustomTableView.swiftfunc addTargetToRefreshControl(_ target: Any?, action: Selector, event: UIControl.Event) { self.refreshControl?.addTarget(target, action: action, for: event) }ViewController.swifttableView.addTargetToRefreshControl(self, action: #selector(self.refreshArticlesAction), event: .valueChanged)
- 下に引っ張る度に
@objc private func refreshArticlesAction()
が呼ばれるので、初期取得数 20 に変更し、Qiita
からデータ取得。(省略しておりますが、初期取得数はQiitaAPI
の取得数になります)refreshControl.endRefreshing()
でローディング停止し、tableView.reloadData()
で内容更新します。3.
TableView
下部までスクロールした際にQiitaAPI
追加取得
ScrollView
のデリゲートメソッドを使い、下までスクロールされたか検出します。distanceFromBottom
何回も呼ばれるのでBool
型の変数reloading
を用意しました。QiitaAPI
の追加取得数は 20 ずつ増やすことにし、100 で上限としました。tableView.reloadData()
で内容更新します。ViewController.swiftfunc scrollViewDidScroll(_ scrollView: UIScrollView) { let height = scrollView.frame.size.height let contentYoffset = scrollView.contentOffset.y let distanceFromBottom = scrollView.contentSize.height - contentYoffset if distanceFromBottom < height { // 何回も呼ばれてしまうので、リロード中は変数:reloadingをtrueにしておく if !reloading && page < 100 { // 取得数を20追加にする if page <= 100 { page += 20 } // リロード処理のフラグ変更を「true」 reloading.toggle() # ・・・省略 (Qiitaからデータ取得する処理) ・・・ // リロード処理のフラグ変更を「false」 reloading.toggle() tableView.reloadData() } } }参考
- 投稿日:2020-11-15T21:06:20+09:00
Xibファイルで作ったUIをViewに読み込む方法
AutoLayoutの制約をなるべく簡単にしたいですよね。
XibをViewに読み込むことでViewController内の制約をよりシンプルに!そして簡略化します。まずは
sampleViewController.Xib
の中にViewを置きます。
このようにViewを置いたらSwiftファイルとViewのXibファイルを作成していきます。
ファイル名は同名にしましょう!!(今回はsampleHomeViewと命名)
sampleHomeView.swift
にコードを書きます。こちらはコピペでOKです。sampleHomeView.swiftimport UIKit class sampleHomeView: UIView { override init(frame: CGRect) { super.init(frame: frame) loadView() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) loadView() } private func loadView() { let className = String(describing: type(of: self)) let view: UIView = Bundle.main.loadNibNamed(className, owner: self, options: nil)?.first as? UIView ?? UIView() view.frame = bounds addSubview(view) } }このコードを書くことでFile's Ownerのクラス設定ができ、Viewに読み込めるようになります。
sampleHomeView.Xib
のFile's Ownerを選択し、ClassをsampleHomeViewにします。
そしたら
sampleHomeView.Xib
にUIパーツを置いていきます。(今回はButtonを配置します)
次に
sampleView.Xib
の設定をしていきます。
一番最初に置いたViewを選択して、CustomClassのClassをsampleHomeViewにします。
これでXibをViewに読み込ませることができるようになりました。
しかし、この状態ではViewに読み込んでいることがわからないのでビルドしましょう!!
これで読み込ませることができましたね(^ ^)
こうすることにより、UIパーツのレイアウトを簡略化できそうです。
- 投稿日:2020-11-15T19:40:16+09:00
【Swift】TabBarをStoryboardReferenceでつなげて、なおかつNavigationControllerも使う方法
調べると載っている・・・けどなんかうまくいかない
あれもこれも機能を付けたい、でもできるだけすっきりとしたStoryboardで作りたいと思って調べたとき、結構つまずいたのでここに残しておきます。
これを読んでくれたあなたの助けになれば幸いです。
※できるだけ初心者向けに詳しく書いていくつもりです。TabBarはStoryboardReferenceを使ってすっきり表示させる
そもそも理想はワンStoryboardワンViewControllerって言う割に、複数のStoryboardをつなげる事が必要なTabBar。
全部コードで書く方は別ですが、初心者にはStoryboardがごちゃごちゃになってしまう危険があります。
そこで登場するのがStoryboardReference。
StoryboardをReference(参照)することができる機能で、表示が小さいため、画面がすっきりします。蛇足話ですが、僕が本当にxcodeで作り始めた頃、一つのStoryboardに全部のViewControllerを詰め込んだら重たくなりすぎて、うまく動作できなくなってしまいました。
やり方
1.StoryboardとViewControllerの準備
まず初めに、xcodeから新しいプロジェクトを作成し、シンプルなAppを選んでください。
ここではProdactNameをTabTestとしておきます。(名前はなんでもいいですよ)
InterfaceはStoryboardにしてください。SwiftUIは知りません。(勉強中)
次に、最初からあるMain.Storyboardは置いておいて、切り替えを行いたいStoryboardを2つ作成します。
Command + N を押すとすぐにnewfileが作れますよ;)
ここではFirstとSecondeと言う名前のStoryboardを作りました。
この二つはまだ空っぽの状態なので、右上の【+】を押して【View Controller】をドラッグドロップしましょう。
続いて、このView Controlerの中身を書くために、またNewFileを作ります。
今度は「Swift File」を選んでください。「cocoa touch class」の方が楽ですが、一つでもコードの記述に慣れるためにこちらを選んでいます。定文なのですぐ覚えれますよ;)
名前はStoryboardに合わせてFirstViewControllerとSecondViewControllerにします。
こんな感じになったと思います。
2.ViewControllerにコードを書く
それでは早速コードを書いていきましょう。
まず最初に記入されているimport Foundation
は消してください。
んで、下記のコードを、日常の挨拶「おはよう」位の定型文として覚えてください。FirstViewController.swiftimport UIKit class FirstViewController: UIViewController { override func viewDidLoad() { } }これは先ほどnewfileを作る時、「cocoa touch class」を選んでいれば自動で記載されている部分です。でも自分で書く事によって、今後に良い影響があると僕は思っています。(少なくとも僕はextension作成に抵抗が無くなったり?同じViewControllerファイル内に別classを書いたりする時に心理的に楽になりました。)
話が逸れました。
簡単に説明すると、まずUIKitと言ういろいろ便利なツール(道具)が入ったキット(道具箱)をimport(読み込み)ます。
次にファイルと同じ名前のクラスを作成することで可読性を高めます。また、このクラスはUIViewControllerとして機能しているクラスと設定しています。
viewDidLoad()部分は最初から「override...」と書き始めるより、「viewd...」くらい書くとxcodeの補填機能が働いて簡単に書けますよ。
このviewDidLoad()は、画面が表示された1回目のみ読み込まれる関数で、いろいろ初期設定をしたりすることが多いです。
super.viewDidLoad()は・・・superです。3.storyboardに作成したclassを設定する
頑張って書いたclassをstoryboardに置いたViewControllerに設定します。
ViewControllerの上にあるバーをクリックすると、右側に詳細画面のようなものが見れるようになります。
この中のCustom classに先ほど作成したFirstViewControllerを設定しましょう。
Secondに関しても同じように設定します。
こうすることでコードが紐付き、プログラムが反映されることになります。さらに、下の図の通りにボタンを押して【Is Initial View Controller】を有効にしておいてください。さらりと書いてますが必須です。
4.TabBarControllerを設置
放置していたMain.storyboardをここで使います。(慣れれば新しくTabBar.storyboardとか作るといいです)
図の順番に押していくと、今あるViewControllerにTabBarが設置できます。
左側がTabBarController、右側がViewControllerですが、右側はいらないのでdeleteで消してください。
TabBarだけが残ります。5.Storyboard Referenceの設置と接続
右上の【+】を押して、Storyboard Referenceをドラッグドロップします。
マウスをTabBarControllerの上に持っていき、controlを押しながらクリックしっぱなしで、Storyboard Referenceまで持っていくとつなげることができます。
ここでつなげ方の種類に選択肢が出てきますが、【Relationship segue】というのを選んでください。
これをもう一度繰り返し、Storyboard Referenceを2つ繋げます。6.Storyboard ReferenceとStoryboardを紐づける
Storyboard Referenceをクリックすると右側の詳細に図のような項目が現れます。
一番上のstoryboardのプルダウンに作成したFirstとSecondがあるので、それぞれ設定します。
これでTabBarがそれぞれのStoryboardに繋がりました。
一度Runしてみてエラーがないか見てみるのもいいでしょう。7.それぞれのstoryboardにtabItemの設定をする
Runした素直な方はわかると思いますが、画面は真っ白です。
下部にほんのわずかに色づいたbarが見えますが、Itemが何も表示されていません。
それぞれのstoryboardにBarItemをおいていきましょう。
First.storyboardに【+】から【Tab Bar Item】をドラッグドロップします。下部に勝手に設置されるので、クリックしてみると、右側の詳細にtitleやimageといった項目が出てきます。適当に入力してみてください。imageはxcodeで用意してくれているものがたくさんあるので、適当に選びます。
secondも設定できたら、もう一度Runしてみてください。
見事Tabbarが設定され、切り替えができています。(どっちを押しても真っ白?そりゃどっちのViewControllerも真っ白い画面だからね;)8.NavigationControllerの設置
ここまできたらゴールは目の前です。もう少し頑張って!
Main.storyboardに戻り、Storyboard Referenceをクリックします。先ほどTabBarControllerをくっつけたEmbed Inの中にNavigation Controllerがあったのを覚えていますか?
それぞれのStoryboard ReferenceにNavigation Controllerをつなげてください。
突然大きなNavigationControllerが出てきてびっくりしますが、大丈夫です。
9.NavigationControllerの設定
ほとんどコードを書いていないので、最後はコードをカッコよく書きましょう。
FirstViewController.swiftimport UIKit class FirstViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setNavigation() } private func setNavigation(){ navigationItem.title = "First" } }SecondViewController.swiftimport UIKit class SecondViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setNavigation() } private func setNavigation(){ navigationItem.title = "Second" } }viewDidLoad()内にsetNavigation()という関数を呼び出します。
こうすることで、viewDidLoadでは何をしているのか、呼び出した先は何をしているのかがわかりやすくなります。
privateとかはググってくださいな。これでRunしてみてください。画面の上部にFirst、Secondと表示されました!
おめでとうございます!よくがんばりました!私が3日間探してたどり着いた事に、あなたは数分でたどり着けましたね!!
終わりに
この記事の大半を深夜3時とかに書いていた為か、変なテンションになりました。
海外のHowTo記事とか見てるとこんな感じで励ましたり、まるで対話してるかのような書き方をしている事が多いので、そんな感じで書いてみました。
スクショも大量に載せたので正直めんd・・・記事が長くなってしまいましたね。私もまだまだ初心者を抜け出せないので、間違いやもっといい方法があればご教授いただけると大好きになります。
冒頭にも書きましたが、この記事が誰かの手助けになれば幸いです。
Coding with me!;)
- 投稿日:2020-11-15T17:59:58+09:00
AlamofireのresponeJsonでJSONを楽に扱う
Alamofireには、JSONを返すAPIを扱うためのresponseJSONというメソッドがあるが、あまり使われていないので紹介。
responseJsonとは?
Apiから返却されたjsonを[Any : Any]のdictionaryへと格納するメソッド。
jsonを型にマッピングする必要がないため、かなり記述量が減る。例えば
`
{
age: 90
}
というjsonが、["age": 90]のようなdictionaryに格納される。
デメリットとしては、型情報が全てAnyで帰ってくるため、バリデーションは自前で実装する必要がある。import Alamofire let url = "https://swapi.dev/api/people/1/" Alamofire.request(url).responseJSON { response in if let json = response.result.value as? NSDictionary { print(json) // 以下が表示される。 // { // "name": "Luke Skywalker", // "height": "172", // "mass": "77", // "hair_color": "blond", // "skin_color": "fair", // "eye_color": "blue", // "birth_year": "19BBY", // "gender": "male", // ... // } } print(response.result) }スターウォーズApiから返却されるJSON { "name": "Luke Skywalker", "height": "172", "mass": "77", "hair_color": "blond", "skin_color": "fair", "eye_color": "blue", "birth_year": "19BBY", "gender": "male", ... }情報を取り出したい場合は以下のように、通常のdictとして扱える。
json["name"] json["hair_corol"]
- 投稿日:2020-11-15T16:32:36+09:00
SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できない問題を解消する
SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できないという現象が発生したのですが、解消することができたのでまとめます。
注意
この解決策は私のプロキシ環境において有効性を確認したものであり、プロキシ環境は会社や組織によって異なるため、この解決策がどの環境にも当てはまるとは限りません。
環境
- MacBook Pro (Retina, 15-inch, Mid 2015)
- macOS Catalina 10.15.7
- Xcode 12.0
発生した現象
- 「SwiftPMでプロジェクトへパッケージを追加する - Qiita」に従って実施したが、リポジトリのパスを入れて[Next]を押した後、しばらくしてからエラー終了する。
- macのプロキシ設定や、インストールされている
git
、curl
のプロキシ設定を見直しても改善しない。解決策
調査の結果、なぜか「追加済みのSwift Packageの依存関係として入っているSwift PackageについてはProxyを超えることができ、インストールできる」ということがわかりました。
つまり、入れたいSwift PackageをDependencyとして追加したSwift Packageをローカルに作り、それをプロジェクトへ追加すればよいです。ローカルのSwift Packageを追加する方法については、SwiftPMの主要開発者によると、ディレクトリをプロジェクトへドラッグアンドドロップすれば追加できるとのことです。それでうまくいけばOKです。
私の環境ではそれだとうまくいかなかったので、このワークアラウンドを使って追加しました。以上です。
- 投稿日:2020-11-15T16:32:36+09:00
SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できない問題を解決する
SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できないという現象が発生したのですが、解決することができたのでまとめます。
注意
この解決策は私のプロキシ環境において有効性を確認したものであり、プロキシ環境は会社や組織によって異なるため、この解決策がどの環境にも当てはまるとは限りません。
環境
- MacBook Pro (Retina, 15-inch, Mid 2015)
- macOS Catalina 10.15.7
- Xcode 12.0
発生した現象
- 「SwiftPMでプロジェクトへパッケージを追加する - Qiita」に従って実施したが、リポジトリのパスを入れて[Next]を押した後、しばらくしてからエラー終了する。
- macのプロキシ設定や、インストールされている
git
、curl
のプロキシ設定を見直しても改善しない。解決策
調査の結果、なぜか「追加済みのSwift Packageの依存関係として入っているSwift PackageについてはProxyを超えることができ、インストールできる」ということがわかりました。
つまり、「入れたいSwift PackageをDependencyとして追加したSwift Packageをローカルに作り、それをプロジェクトへ追加する」ことで解決できます。
ローカルのSwift Packageを追加する方法については、SwiftPMの主要開発者によると、ディレクトリをプロジェクトへドラッグアンドドロップすれば追加できるとのことです。それでうまくいけばOKです。
私の環境ではそれだとうまくいかなかったので、このワークアラウンドを使って追加しました。以上です。
- 投稿日:2020-11-15T16:13:22+09:00
SwiftPMでローカルにあるSwift Packageをプロジェクトへ追加する
SwiftPMで、ローカルにあるSwift Packageをプロジェクトへ追加したい場合があります。例えば、プロジェクトと依存パッケージを単一のリポジトリで扱いたい場合などです(モノレポ)。
SwiftPMの主要開発者の回答によると、プロジェクトへパッケージをドラッグアンドドロップすればOKとのことですが、少なくとも私の環境ではうまくいかない状況でした(この回答が「あるべき動き」だろうから、今後のアップデートで解消されていくとは思われますが)
この記事では、私の環境における現状(2020/11/15)のワークアラウンドをまとめます。
環境
- MacBook Pro (Retina, 15-inch, Mid 2015)
- macOS Catalina 10.15.7
- Xcode 12.0
手順
- 追加したいSwift Packageを、ローカルのgitリポジトリにする。このgitリポジトリはあとで消すので、やり方は適当で構いません(
git init; git add . ;git commit -m "init"
する)- 追加したいSwift Packageのディレクトリを、プロジェクトフォルダ内のどこかに置きます。
- 追加したいSwift Packageのディレクトリを、Xcodeプロジェクトにドラッグ&ドロップします。
- Xcodeのメニューから、[File] - [Swift Package] - [Add Package Dependency] と進み、当該のSwift Packageのパスを指定します。「file:// + フルパス」の形です。例えば
file:///Users/n/d/expProject/expdat/swiftPackage
などとなります。- パスを修正します。テキストファイルとして.pbxprojを開き、
file://
で検索 して4.で指定したパスを見つけ、相対パスに置き換えます。例えば./expdat/swiftPackage
などとします。- Swift Packageのディレクトリから
.git
ディレクトリを削除します。
4.
の時点で、すでにプロジェクトへの追加は完了していますが、ローカルでしか扱えず、Gitとかにpushができない状態になります。それを解消するために5.
、6.
の手順を行っています。以上です。
- 投稿日:2020-11-15T15:28:29+09:00
SwiftPMでプロジェクトへパッケージを追加する
- 投稿日:2020-11-15T10:50:12+09:00
[Swift5]プロトコルを使ってModelからControllerへ値を返す方法
はじめに
今回の記事は前回投稿した[Swift5]ViewControllerからModelに通信を行って値を渡す方法の続編です。まだ見てない方は先にコチラ▼を見てからの方が良いかと思います。
https://qiita.com/nkekisasa222/items/dabe806c23d3890a009fそれでは本題に入りましょう!
プロトコルの作成
まずコードを記述します。
SampleModel.swift//①ここでプロトコルの作成 protocol DoneCatchProtocol { //②Controllerに返したい値と型を指定(今回はString) func catchSampleData(sampleValueA: String, sampleValueB: String, sampleValueC: String) } class SampleModel { var sampleValueA: String? var sampleValueB: String? var sampleValueC: String? //③プロトコルのインスタンスを作成 var doneCatchProtocol: DoneCatchProtocol? init(firstSampleValue: String, secondSampleValue: String, thirdSampleValue: String) { sampleValueA = firstSampleValue sampleValueB = secondSampleValue sampleValueC = thirdSampleValue } //④Model内で処理を行うのでメソッドを作成 func processingSampleModel { //⑤ここで作成したプロトコルを呼び出して値を入れる self.doneCatchProtocol?.catchSampleData(sampleValueA: sampleValueA, sampleValueB: sampleValueB, sampleValueC: sampleValueC) }流れをまとめます。
①Model側でプロトコルを作成します。 ②返したい値のキー値と型を指定してあげる。 ③プロトコルのインスタンスを作成。 ④Model内で処理を行うのでメソッドを作成 ⑤プロトコルを呼び出してControllerで呼び出したい値を指定する。Controller側の記述
まずコードを記述します。
SampleViewController.swift//①Controllerでプロトコルを扱えるように呼び出します。 class SampleViewController: UIViewController, DoneCatchProtocol { firstSampleValue = "firstSampleValue" secondSampleValue = "secondSampleValue" thirdSampleValue = "thirdSampleValue" //④SampleModelから返ってくる値を代入するプロパティ var valueA: String? var valueB: String? var valueC: String? override func viewDidLoad() { super.viewDidLoad() startSampleModel() } func startSampleModel() { let sampleModel = SampleModel(firstSampleValue: firstSampleValue, secondSampleValue: secondSampleValue, thirdSampleValue: thirdSampleValue) //③SampleModelのプロトコルに委託とメソッドの呼び出し sampleModel.doneCatchProtocol = self sampleModel.processingSampleMode() } //②Controller側でプロトコルを呼び出すと自動生成されます。 func catchSampleData(sampleValueA: String, sampleValueB: String, sampleValueC: String) { //⑤SampleModelから返ってきた値を代入する valueA = sampleValueA valueB = sampleValueB valueC = sampleValueC } }流れをまとめます。
①Controllerでプロトコルを扱えるように呼び出す。 ②①でプロトコルを呼び出すとメソッドが自動生成されます。 ③SampleModelのプロトコルに委託とメソッドの呼び出し ④SampleModelから返ってくる値を代入するプロパティを用意 ⑤SampleModelから返ってきた値を代入するこれでModelで処理した値をControllerで使用できますね。
最後に
前回記事と今回の記事でControllerからModelに通信を行い、Modelで処理した内容をControllerへ返してそれをControllerで扱う方法について投稿しました。MVCモデルではよく扱われる開発方法なのでは?と思いますので、是非参考にしていただけたらと思います。
最後までご覧いただきありがとうございました!
- 投稿日:2020-11-15T09:51:39+09:00
【Swift】compareについて考えてみた
これは初学者である筆者が初めて
compare
と出会った時の思考を綴るものとなっております。ここでいうcompareはSwiftのStringクラスインスタンスメソッド
compare
を指します。compareの返り値って何なの問題
ComparisonResult
ですね。はい、おしまい。・・・とならないのが初学者です。
私ももれなくその一人でした。
AppleDeveloperドキュメントにも、目立つくらいReturn Value
って書いてあるのに!そんな初歩的なところでつまづいている訳ですが、試行錯誤の過程は良い勉強になったのでこちらにアウトプットという形で残すことにしました。
どこで出会ったのか?
Swift実践入門 第3版 P53
Foundationによる高度な操作 見本コードにて上記箇所となります。
swiftの学習にと、こちらの書籍を手に取る初学者の方も多いのではないでしょうか。
私は上記箇所で突然現れた次のコードをみて固まってしまいました。// P53 見本コード import Foundation // compareはFoundationというライブラリで提供されているメソッド // 2つの文字列間の順序の比較 let options = String.CompareOptions.caseInsensitive let order = "abc".compare("ABC", options: options) order == ComparisonResult.orderedSame //true上記のコードでは、compare(_:option)メソッドで2つの文字列間の順序を検証しています。
--略-- 結果として順序が同じであることを意味する.orderSameという値を得ています。一読しただけではよく分かりませぬ。
コードリーディングしてみる
まず1行目
let options = String.CompareOptions.caseInsensitiveこれはConpareメソッドに用意されているオプションで、大文字と小文字を区別しないというもの。
これを定数optionsとして定義している。2行目
let order = "abc".compare("ABC", options: options)右辺では、文字列"abc"に対してcompareメソッドを使用し()内の"ABC"との比較をしている。その際、引数として先ほど定義した
options
を受け取っている。
ここで得られた結果を、定数order
として定義している。3行目
order == ComparisonResult.orderedSame2行目で定義した
order
とComparisonResult.orderedSame
を比較演算子にかけ、trueが返ってくる。細かな内容について考えてみる
上記を踏まえ少しコードを簡素にした。
オプションについては疑問点とは関係なさそうなので省略。let order = "abc".compare("abc") order == ComparisonResult.orderedSame // trueつまり、
"abc".compare("abc")
の返り値はComparisonResult.orderedSame
ということ?確認してみた。
let hoge = "abc".compare("abc") print(hoge) // NSComparisonResult実行結果・・・
NSComparisonResult
ComparisonResultとは
見本コードでも何食わぬ顔で現れたこいつを理解する必要がありそう。
調べてみると
ComparisonResult
は列挙型で、3つのcase定数を持つということが分かった。enum ComparisonResult: Int { case orderedAscending = -1 case orderedSame = 0 case orderedDescending = 1 }ということはcompareメソッドはcaseのどれかを取得した後、ComparisonResultを返すということになると考えられる。
- case orderedAscending ・・・
<
- case orderedSame ・・・
=
- case orderedDescending ・・・
>
今回は比較対象がイコールであったため、
orderedSame
という値を取得し、
ComparisonResult.orderedSame
が返されたということになります。compareの返り値
ということで冒頭のコードに戻ってみる。
せっかくなので、"ABC"だったところを"DEF"に変えてみる。let options = String.CompareOptions.caseInsensitive let order = "abc".compare("DEF", options: options) order == ComparisonResult.orderedAscending // true2行目の
"abc".compare("DEF", options: options)
の返り値がComparisonResult.orderedAscending
なのでtrueということとなる。
abc < DEF
という比較結果となっています。後語り
今、自分で書いていても、「なぜこんなことが気になったのだろう?」と感じてしまうような内容ですね・・・
思いつく理由としては、
メソッドの返り値が列挙型
ということに戸惑いがあったからなのかもしれません。別のメソッドですが、以下のようなシンプルな挙動をイメージしていました。
私が期待していたようなコード及び実行結果// 私が期待していたようなコード及び実行結果 "abc".isEqual("abc") // true今回も上記のような形が頭から離れず「比較系メソッドはtrue/falseが返り値となるものだ」という先入観により、つまづき理解に苦しんでしまいました。
結論:さっさと公式ドキュメント読めばよかった。
おしまい。
ここまでお読みくださりありがとうございましたm(__)m
- 投稿日:2020-11-15T08:03:54+09:00
【Swift】'hitTest(_:types:)' was deprecated in iOS 14.0 対処法
iOS 14.0から
hitTest(_:types:)
は非推奨になっており、Xcodeから注意された時の対処法
動作環境 バージョン Xcode 12.1 iOS 14.1 Swift 5
iOS 14.0で
hitTest(_:types:)
を使用すると、このように注意されます。
・日本語訳
'hitTest(_:types:)' は iOS 14.0では非推奨です。
ARSCVView.raycastQuery(from point: CGPoint, allowing target: ARRaycastQuery.Target, alignment: ARRaycastQuery.TargetAlignment)
こちらを使ってね。ということです。
hitTest(_:types:)の代わりにraycastQueryを使用
touchesBegan
でtouchした座標を取得するコードを例として挙げさせていただきました。override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // タッチした最初の座標を取り出す guard let touch = touches.first else { return } // タッチで取得した座標をスクリーン座標系に変換 let touchPosition = touch.location(in: sceneView) //これより下がhitTest(_:types:)の代わりのコード guard let query = sceneView.raycastQuery(from: touchPosition, allowing: .existingPlaneGeometry, alignment: .any) else { return } let results = sceneView.session.raycast(query) guard let hitTestResult = results.first else { print("no surface found") return } let anchor = ARAnchor(transform: hitTestResult.worldTransform) sceneView.session.add(anchor: anchor) }
これで意図した動作が出来ました。
間違いやより良い方法がありましたら、優しく指摘していただけると幸いです。参考
- 投稿日:2020-11-15T08:03:54+09:00
'hitTest(_:types:)' was deprecated in iOS 14.0 対処法
iOS 14.0から
hitTest(_:types:)
は非推奨になっており、Xcodeから注意された時の対処法
動作環境 バージョン Xcode 12.1 iOS 14.1 Swift 5
iOS 14.0で
hitTest(_:types:)
を使用すると、このように注意されます。
・日本語訳
'hitTest(_:types:)' は iOS 14.0では非推奨です。
ARSCVView.raycastQuery(from point: CGPoint, allowing target: ARRaycastQuery.Target, alignment: ARRaycastQuery.TargetAlignment)
こちらを使ってね。ということです。
hitTest(_:types:)の代わりにraycastQueryを使用
touchesBegan
でtouchした座標を取得するコードを例として挙げさせていただきました。override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // タッチした最初の座標を取り出す guard let touch = touches.first else { return } // タッチで取得した座標をスクリーン座標系に変換 let touchPosition = touch.location(in: sceneView) //これより下がhitTest(_:types:)の代わりのコード guard let query = sceneView.raycastQuery(from: touchPosition, allowing: .existingPlaneGeometry, alignment: .any) else { return } let results = sceneView.session.raycast(query) guard let hitTestResult = results.first else { print("no surface found") return } let anchor = ARAnchor(transform: hitTestResult.worldTransform) sceneView.session.add(anchor: anchor) }
これで意図した動作が出来ました。
間違いやより良い方法がありましたら、優しく指摘していただけると幸いです。参考
- 投稿日:2020-11-15T08:03:54+09:00
【ARKit】'hitTest(_:types:)' was deprecated in iOS 14.0 対処法
iOS 14.0から
hitTest(_:types:)
は非推奨になっており、Xcodeから注意された時の対処法
動作環境 バージョン Xcode 12.1 iOS 14.1 Swift 5
iOS 14.0で
hitTest(_:types:)
を使用すると、このように注意されます。
・日本語訳
'hitTest(_:types:)' は iOS 14.0では非推奨です。
ARSCVView.raycastQuery(from point: CGPoint, allowing target: ARRaycastQuery.Target, alignment: ARRaycastQuery.TargetAlignment)
こちらを使ってね。ということです。
hitTest(_:types:)の代わりにraycastQueryを使用
touchesBegan
でtouchした座標を取得するコードを例として挙げさせていただきました。override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { // タッチした最初の座標を取り出す guard let touch = touches.first else { return } // タッチで取得した座標をスクリーン座標系に変換 let touchPosition = touch.location(in: sceneView) //これより下がhitTest(_:types:)の代わりのコード guard let query = sceneView.raycastQuery(from: touchPosition, allowing: .existingPlaneGeometry, alignment: .any) else { return } let results = sceneView.session.raycast(query) guard let hitTestResult = results.first else { print("no surface found") return } let anchor = ARAnchor(transform: hitTestResult.worldTransform) sceneView.session.add(anchor: anchor) }
これで意図した動作が出来ました。
間違いやより良い方法がありましたら、優しく指摘していただけると幸いです。参考