20201115のSwiftに関する記事は15件です。

swift実践入門output Chapter5  後編

制御構文

条件分岐、繰り返し、遅延実行、パターンマッチ
プログラムの実行フローを制御する構文。制御フローの制御には条件分岐や繰り返しなどがあり、それを組み合わせ実行し自在に操る

for-in文 シーケンスの要素の列挙

chapter5.swift
let array = [1,2,3]
for element in array {
    print(element)
}

Dictionary

chapter5.swift
let dictionary = ["a": 1, "b": 2]
for (key,value) in dictionary {
    print("Key: \(key), vlue: \(value)")
}

while文 継続条件による繰り返

chapter5.swift
var f = 1
while f < 4 {
    print(f)
    f += 1
}

repeat while文

while文は実行前に条件式を評価するため、場合によっては一度も実行されない
そこで条件式の成否にかかわらず、必ず1回以上繰り返しを実行したい場合は、repear whieを使用

chapter5.swift
var t = 1
repeat {
    print(t)
    t += 3
}
while t < 1

実行中の中断

break文は繰り返し文全体を終了させます。

chapter5.swift
var 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.swift
var 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.swift
var count = 0

func someFUNTIONS() -> Int{
    defer {
        count += 1
    }
    return count
}
print(someFUNTIONS())
print(someFUNTIONS())
print(count)

パターンマッチ 値の構造や性質による評価

swiftには値の持つ構造や性質を表現するパターンという概念がある
パターン待ちとは、値が特定のパターンに合致するかの検査をすること

式パターン  ~ = 演算子による評価

chapter5.swift
let integer = 6

switch integer {
case 6:
    print("match: 6")

case 5...10:
    print("match: 5...10")

default:
    print("default")
}

バリューバインディング 値の代入を伴う評価

chapter5.swift
let values = 3

switch values {
case let matchValue :
    print(matchValue)
}

オプショナルパターン Optional型の値の有無の評価

chapter5.swift
let values = 3
let optionalD = Optional(4)
switch optionalD{
case let f?:
    print(f)

default:
    print("nil")
}

}

列挙型ケースパターン ケースとの一致の評価

chapter5.swift
enum 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.swift
let any: Any = "fafafa"
switch any {
case is String:
    print("match: String")
case is Int:
    print("match: Int")
default:
    print("default")
}

}

as演算子による型キャスティングパターン 型のキャストによる評価

chapter5.swift
let 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.swift
let 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.swift
var nextValue = Optional(1)
while case let value? = nextValue {
    print("value:\(value)")

    if value >= 8 {
        nextValue = nil
    } else {
        nextValue = value + 1
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

swift実践入門output Chapter5

制御構文

条件分岐、繰り返し、遅延実行、パターンマッチ
プログラムの実行フローを制御する構文。制御フローの制御には条件分岐や繰り返しなどがあり、それを組み合わせ実行し自在に操る

if 文 条件の成否による分岐

chapter5.swift
let value = 5

if value <= 4 {
    print("valueは4以下")
}else {
    print("valueは5以上")
}

if let文 値の有無による分岐

オプショナル型とは変数にnilの代入を許容するデータ型で、反対に非オプショナル型はnilを代入できません。オプショナル型の変数にはデータ型の最後に「?」か「!」をつけます。

chapter5.swift
let 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.swift
func printIfPositive(_ a: Int) {
    guard a > 0 else {
        return
    }
    print(a)
}
printIfPositive(6)

guard文で宣言された変数や定数へのアクセス

guard let文とif letぶんの違いは
guard let文で宣言された変数や定数は guard let文以降で利用可能という点

chapter5.swift
func 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.swift
let r = -1
switch  r {
case Int.min ..< 0:
    print("rは負の数")

case 1 ..< Int.max:
    print("rは正の数")
default:
    print("aは0")
}

ケースの網羅性のチェック

chapter5.swift
enum 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.swift
let 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
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TableViewでのデータの読込・リフレッシュ・更新についての処理

はじめに

Qiitaが提供しているAPIを使用して、TableViewに記事を表示する際に、データの読み込みリフレッシュ更新について実装しましたので備忘録として投稿します。

概要

  1. QiitaAPIの初期取得
  2. 取得しているQiitaAPIのリフレッシュ
  3. TableView下部までスクロールした際にQiitaAPI追加取得

※上記にフォーカスをあたて説明になります。

動作環境

【Xcode】Version 12.0.1
【Swift】Version 5.3

実装コード

CustomTableView.swift
import 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.swift
class 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の初期取得

output1.gif

  • viewDidLoadrefreshControl.beginRefreshing()を呼び出してローディング開始。
  • Qiitaからデータ取得。
  • refreshControl.endRefreshing()でローディング停止し、tableView.reloadData()で内容更新します。

2. 取得しているQiitaAPIのリフレッシュ

output3.gif

  • UIRefreshControlのインスタンスにaddTargetで指定しています。
CustomTableView.swift
func addTargetToRefreshControl(_ target: Any?, action: Selector, event: UIControl.Event) {
    self.refreshControl?.addTarget(target, action: action, for: event)
}
ViewController.swift
tableView.addTargetToRefreshControl(self, action: #selector(self.refreshArticlesAction), event: .valueChanged)
  • 下に引っ張る度に@objc private func refreshArticlesAction()が呼ばれるので、初期取得数 20 に変更し、Qiitaからデータ取得。(省略しておりますが、初期取得数はQiitaAPIの取得数になります)
  • refreshControl.endRefreshing()でローディング停止し、tableView.reloadData()で内容更新します。

3. TableView下部までスクロールした際にQiitaAPI追加取得

output2.gif

  • ScrollViewのデリゲートメソッドを使い、下までスクロールされたか検出します。
  • distanceFromBottom何回も呼ばれるのでBool型の変数reloadingを用意しました。
  • QiitaAPIの追加取得数は 20 ずつ増やすことにし、100 で上限としました。
  • tableView.reloadData()で内容更新します。
ViewController.swift
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()
        }
    }
}

参考

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

Xibファイルで作ったUIをViewに読み込む方法

AutoLayoutの制約をなるべく簡単にしたいですよね。
XibをViewに読み込むことでViewController内の制約をよりシンプルに!そして簡略化します。

まずはsampleViewController.Xibの中にViewを置きます。
スクリーンショット 2020-11-14 12.09.12.png

このようにViewを置いたらSwiftファイルとViewのXibファイルを作成していきます。

Swiftファイル
スクリーンショット 2020-11-14 11.55.32.png
ViewのXibファイル
スクリーンショット 2020-11-15 21.04.17.png

ファイル名は同名にしましょう!!(今回はsampleHomeViewと命名)

sampleHomeView.swiftにコードを書きます。こちらはコピペでOKです。

sampleHomeView.swift
import 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にします。
スクリーンショット 2020-11-15 20.51.38.png

そしたらsampleHomeView.XibにUIパーツを置いていきます。(今回はButtonを配置します)
スクリーンショット 2020-11-14 12.01.59.png

次にsampleView.Xibの設定をしていきます。
一番最初に置いたViewを選択して、CustomClassのClassをsampleHomeViewにします。
スクリーンショット 2020-11-15 20.55.59.png
これでXibをViewに読み込ませることができるようになりました。
しかし、この状態ではViewに読み込んでいることがわからないのでビルドしましょう!!
スクリーンショット 2020-11-14 12.09.28.png

これで読み込ませることができましたね(^ ^)
こうすることにより、UIパーツのレイアウトを簡略化できそうです。

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

【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は知りません。(勉強中)
スクリーンショット 2020-11-15 2.15.26.png

スクリーンショット 2020-11-15 2.18.40.png
(名前が恥ずかしいので隠してます)

次に、最初からあるMain.Storyboardは置いておいて、切り替えを行いたいStoryboardを2つ作成します。
Command + N を押すとすぐにnewfileが作れますよ;)
ここではFirstとSecondeと言う名前のStoryboardを作りました。
この二つはまだ空っぽの状態なので、右上の【+】を押して【View Controller】をドラッグドロップしましょう。
スクリーンショット 2020-11-15 2.32.24.png
続いて、このView Controlerの中身を書くために、またNewFileを作ります。
今度は「Swift File」を選んでください。「cocoa touch class」の方が楽ですが、一つでもコードの記述に慣れるためにこちらを選んでいます。定文なのですぐ覚えれますよ;)
名前はStoryboardに合わせてFirstViewControllerとSecondViewControllerにします。
こんな感じになったと思います。
スクリーンショット 2020-11-15 2.41.22.png

2.ViewControllerにコードを書く

それでは早速コードを書いていきましょう。
まず最初に記入されているimport Foundationは消してください。
んで、下記のコードを、日常の挨拶「おはよう」位の定型文として覚えてください。

FirstViewController.swift
import 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に関しても同じように設定します。
スクリーンショット 2020-11-15 18.23.26.png
こうすることでコードが紐付き、プログラムが反映されることになります。

さらに、下の図の通りにボタンを押して【Is Initial View Controller】を有効にしておいてください。さらりと書いてますが必須です。
スクリーンショット 2020-11-15 18.30.33.png

4.TabBarControllerを設置

放置していたMain.storyboardをここで使います。(慣れれば新しくTabBar.storyboardとか作るといいです)
図の順番に押していくと、今あるViewControllerにTabBarが設置できます。
スクリーンショット 2020-11-15 18.34.58.png
左側がTabBarController、右側がViewControllerですが、右側はいらないのでdeleteで消してください。
TabBarだけが残ります。

5.Storyboard Referenceの設置と接続

右上の【+】を押して、Storyboard Referenceをドラッグドロップします。
スクリーンショット 2020-11-15 18.56.19.png
マウスをTabBarControllerの上に持っていき、controlを押しながらクリックしっぱなしで、Storyboard Referenceまで持っていくとつなげることができます。
ここでつなげ方の種類に選択肢が出てきますが、【Relationship segue】というのを選んでください。
スクリーンショット 2020-11-15 19.00.02.png
これをもう一度繰り返し、Storyboard Referenceを2つ繋げます。

6.Storyboard ReferenceとStoryboardを紐づける

Storyboard Referenceをクリックすると右側の詳細に図のような項目が現れます。
一番上のstoryboardのプルダウンに作成したFirstとSecondがあるので、それぞれ設定します。
スクリーンショット 2020-11-15 19.05.09.png

これでTabBarがそれぞれのStoryboardに繋がりました。
一度Runしてみてエラーがないか見てみるのもいいでしょう。

7.それぞれのstoryboardにtabItemの設定をする

Runした素直な方はわかると思いますが、画面は真っ白です。
下部にほんのわずかに色づいたbarが見えますが、Itemが何も表示されていません。
それぞれのstoryboardにBarItemをおいていきましょう。
First.storyboardに【+】から【Tab Bar Item】をドラッグドロップします。下部に勝手に設置されるので、クリックしてみると、右側の詳細にtitleやimageといった項目が出てきます。適当に入力してみてください。imageはxcodeで用意してくれているものがたくさんあるので、適当に選びます。
スクリーンショット 2020-11-15 19.13.45.png
secondも設定できたら、もう一度Runしてみてください。
見事Tabbarが設定され、切り替えができています。(どっちを押しても真っ白?そりゃどっちのViewControllerも真っ白い画面だからね;)

8.NavigationControllerの設置

ここまできたらゴールは目の前です。もう少し頑張って!
Main.storyboardに戻り、Storyboard Referenceをクリックします。先ほどTabBarControllerをくっつけたEmbed Inの中にNavigation Controllerがあったのを覚えていますか?
それぞれのStoryboard ReferenceにNavigation Controllerをつなげてください。
突然大きなNavigationControllerが出てきてびっくりしますが、大丈夫です。
スクリーンショット 2020-11-15 19.26.13.png

9.NavigationControllerの設定

ほとんどコードを書いていないので、最後はコードをカッコよく書きましょう。

FirstViewController.swift
import UIKit

class FirstViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        setNavigation()
    }

    private func setNavigation(){
        navigationItem.title = "First"
    }
}
SecondViewController.swift
import 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!;)

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

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"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できない問題を解消する

SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できないという現象が発生したのですが、解消することができたのでまとめます。

注意

この解決策は私のプロキシ環境において有効性を確認したものであり、プロキシ環境は会社や組織によって異なるため、この解決策がどの環境にも当てはまるとは限りません。

環境

  • MacBook Pro (Retina, 15-inch, Mid 2015)
  • macOS Catalina 10.15.7
  • Xcode 12.0

発生した現象

  • SwiftPMでプロジェクトへパッケージを追加する - Qiita」に従って実施したが、リポジトリのパスを入れて[Next]を押した後、しばらくしてからエラー終了する。
  • macのプロキシ設定や、インストールされているgitcurlのプロキシ設定を見直しても改善しない。

解決策

調査の結果、なぜか「追加済みのSwift Packageの依存関係として入っているSwift PackageについてはProxyを超えることができ、インストールできる」ということがわかりました。
つまり、入れたいSwift PackageをDependencyとして追加したSwift Packageをローカルに作り、それをプロジェクトへ追加すればよいです。

ローカルのSwift Packageを追加する方法については、SwiftPMの主要開発者によると、ディレクトリをプロジェクトへドラッグアンドドロップすれば追加できるとのことです。それでうまくいけばOKです。
私の環境ではそれだとうまくいかなかったので、このワークアラウンドを使って追加しました。

以上です。

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

SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できない問題を解決する

SwiftPMがプロキシを越えられず、プロジェクトへパッケージが追加できないという現象が発生したのですが、解決することができたのでまとめます。

注意

この解決策は私のプロキシ環境において有効性を確認したものであり、プロキシ環境は会社や組織によって異なるため、この解決策がどの環境にも当てはまるとは限りません。

環境

  • MacBook Pro (Retina, 15-inch, Mid 2015)
  • macOS Catalina 10.15.7
  • Xcode 12.0

発生した現象

  • SwiftPMでプロジェクトへパッケージを追加する - Qiita」に従って実施したが、リポジトリのパスを入れて[Next]を押した後、しばらくしてからエラー終了する。
  • macのプロキシ設定や、インストールされているgitcurlのプロキシ設定を見直しても改善しない。

解決策

調査の結果、なぜか「追加済みのSwift Packageの依存関係として入っているSwift PackageについてはProxyを超えることができ、インストールできる」ということがわかりました。

つまり、「入れたいSwift PackageをDependencyとして追加したSwift Packageをローカルに作り、それをプロジェクトへ追加する」ことで解決できます。

ローカルのSwift Packageを追加する方法については、SwiftPMの主要開発者によると、ディレクトリをプロジェクトへドラッグアンドドロップすれば追加できるとのことです。それでうまくいけばOKです。
私の環境ではそれだとうまくいかなかったので、このワークアラウンドを使って追加しました。

以上です。

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

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

手順

  1. 追加したいSwift Packageを、ローカルのgitリポジトリにする。このgitリポジトリはあとで消すので、やり方は適当で構いません(git init; git add . ;git commit -m "init"する)
  2. 追加したいSwift Packageのディレクトリを、プロジェクトフォルダ内のどこかに置きます。
  3. 追加したいSwift Packageのディレクトリを、Xcodeプロジェクトにドラッグ&ドロップします。
  4. Xcodeのメニューから、[File] - [Swift Package] - [Add Package Dependency] と進み、当該のSwift Packageのパスを指定します。「file:// + フルパス」の形です。例えばfile:///Users/n/d/expProject/expdat/swiftPackageなどとなります。
  5. パスを修正します。テキストファイルとして.pbxprojを開き、file://で検索 して4.で指定したパスを見つけ、相対パスに置き換えます。例えば./expdat/swiftPackageなどとします。
  6. Swift Packageのディレクトリから.gitディレクトリを削除します。

4.の時点で、すでにプロジェクトへの追加は完了していますが、ローカルでしか扱えず、Gitとかにpushができない状態になります。それを解消するために5.6.の手順を行っています。

以上です。

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

SwiftPMでプロジェクトへパッケージを追加する

SwiftPMでプロジェクトへパッケージを追加する方法についてまとめます。

環境

  • MacBook Pro (Retina, 15-inch, Mid 2015)
  • macOS Catalina 10.15.7
  • Xcode 12.0

手順

1. Xcodeで、[File] - [Swift Package] - [Add Package Dependency] と進み、

スクリーンショット 2020-11-15 15.12.30.png

2. Swift Packageのリポジトリ名を入れて(ここではPromiseKit)、[Next]を押す

スクリーンショット 2020-11-15 15.13.48.png

あとは指示に従って進めば追加できます。

以上です。

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

[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モデルではよく扱われる開発方法なのでは?と思いますので、是非参考にしていただけたらと思います。

最後までご覧いただきありがとうございました!

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

【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.orderedSame

2行目で定義したorderComparisonResult.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

調べてみると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
// true

2行目の"abc".compare("DEF", options: options)の返り値がComparisonResult.orderedAscendingなのでtrueということとなる。
abc < DEFという比較結果となっています。

後語り

今、自分で書いていても、「なぜこんなことが気になったのだろう?」と感じてしまうような内容ですね・・・

思いつく理由としては、メソッドの返り値が列挙型ということに戸惑いがあったからなのかもしれません。

別のメソッドですが、以下のようなシンプルな挙動をイメージしていました。
私が期待していたようなコード及び実行結果

// 私が期待していたようなコード及び実行結果
"abc".isEqual("abc")
// true

今回も上記のような形が頭から離れず「比較系メソッドはtrue/falseが返り値となるものだ」という先入観により、つまづき理解に苦しんでしまいました。

結論:さっさと公式ドキュメント読めばよかった。

おしまい。

ここまでお読みくださりありがとうございましたm(__)m

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

【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:)を使用すると、このように注意されます。
スクリーンショット 2020-11-15 7.11.17.png

・日本語訳
'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)
    }



これで意図した動作が出来ました。
間違いやより良い方法がありましたら、優しく指摘していただけると幸いです。

参考

stackoverflow: 'hitTest()' was Depecrated in iOS 14.0

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

'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:)を使用すると、このように注意されます。
スクリーンショット 2020-11-15 7.11.17.png

・日本語訳
'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)
    }



これで意図した動作が出来ました。
間違いやより良い方法がありましたら、優しく指摘していただけると幸いです。

参考

stackoverflow: 'hitTest()' was Depecrated in iOS 14.0

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

【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:)を使用すると、このように注意されます。
スクリーンショット 2020-11-15 7.11.17.png

・日本語訳
'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)
    }



これで意図した動作が出来ました。
間違いやより良い方法がありましたら、優しく指摘していただけると幸いです。

参考

stackoverflow: 'hitTest()' was Depecrated in iOS 14.0

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