20201212のSwiftに関する記事は17件です。

[Swift] 関数 外部引数の省略

はじめに

関数内の_について良く分からないままスルーして勉強していたので、まとめてみます。
短いですが、、、

書き方

省略には外部引数に_を使用します。
関数を使う時も関数(外部引数: 仮引数)関数(実引数)になります。

func sum(_ int1: Int, _ int2: Int) -> Int {
    return int1 + int2
}
let result = sum(1, 2)   //3

参考文献

石川洋資・西川勇世、『[増補改訂第3版] Swift実践入門 直感的な文法と安全性を兼ね備えた言語』、技術評論社、2020年4月28日、453ページ

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

[Swift] 関数の外部引数と内部引数

定義

外部引数とは関数呼び出し時に使用する引数。
内部引数とは関数内で使用される引数。

書き方

以下のコードは、外部引数をheightとwidthで内部引数をhとwにしています。
外部引数と内部引数の間は半角スペースを1つ開けます。
外部引数は関数を利用する側から見てわかりやすい名前を書き、
内部引数はプログラムが冗長にならない無駄のない名前を書くそうです。

func menseki(height h: Double,width w: Double) -> Double {
    return h * w / 2
}
let kekka = menseki(height: 6, width: 3)   //9

メリット

関数を使う側には分かりやすく書けて、関数内では短かく書く事ができるので手間が省けるのかなと思います。

参考文献

石川洋資・西川勇世、『[増補改訂第3版] Swift実践入門 直感的な文法と安全性を兼ね備えた言語』、技術評論社、2020年4月28日、453ページ

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

BLEで始める心臓の死活監視

Applibot Advent Calendar 2020」 13日目の記事になります。
前日は@mrpcさんの配信・中継で「音質」にこだわりたいときという記事でした!

はじめに

あなたの心臓は動いていますか?
心臓の動きを見ることで自身の状態を知ることができます。

学生時代に緊張している時の心拍数を計測したくてMacの常駐アプリを作りました。
それを使って最終面接中に心拍数を見せて緊張してることをアピールして入社したのが今の会社です!

今回はBLEの概要とCore Bluetoothフレームワークを使って心拍センサーと接続して心拍数を取得する方法を説明します。

BLE(Bluetooth Low Energy)概要

  • セントラル・・・・スキャン・接続を行う側
    • 今回のアプリではMac
  • ペリフェラル・・・スキャン・接続される側
    • 今回のアプリでは心拍センサー

ペリフェラルは接続待ちの間、アドバタイズと呼ばれるブロードキャスト型の通信で自身のデータを発信します。
セントラルはスキャンすることでアドバタイズを受信して周囲のペリフェラルを見つけます。

  • GATT(汎用属性プロファイル)
    • データの送受信や構造について定義したもの
  • サービス
    • GATTに定義されていて、複数のキャラクタリスティックをグループ化したもの
  • キャラクタリスティック
    • データ構造や送受信方法が定義されている
    • Read/Write/Notifyの属性を持っていて、属性にない操作はできない

セントラルは目的のペリフェラルと接続成功するとGATT通信を用いて以下の手順でデータの読み書きを行います。

  1. GATTサーバーに接続する
  2. サービスを検索する
  3. キャラクタリスティックを検索する
  4. 目的のキャラクタリスティックに対してデータの読み書きをする

Heart Rate Profile

Bluetooth SIGで定義されている心拍計のためのプロファイルで心拍計の機能に関するHeart Rate Serviceとデバイスの情報に関するDevice Information Serviceの2つのサービスを持っています。

Heart Rate Service

短縮UUID : 180D

心拍数を測定するHeart Rate Measurementやセンサを身体のどこにつけるかを示すBody Sensor Location、心拍計を操作するためのHeart Rate Control Pointなどのキャラクタリスティックを持っています。

Heart Rate Measurement Characteristic

短縮UUID : 2A37

Notify属性を持っていて、センサーデータが更新された時に通知されます。
データ仕様は以下のようになっています。

  1. フラグ(8bit)
    1. 心拍数フォーマット(1bit)
      • 0: uint8
      • 1: uint16
    2. センサー接触状態(2bit)
      • 0: 機能をサポートしていない
      • 1: 機能をサポートしていない
      • 2: 機能はサポートしているが、接触を検知できない
      • 3: 機能をサポートしていて、接触を検知している
    3. 消費電力状態(1bit)
      • 0: 値なし
      • 1: 値あり
    4. RRI(1bit)
      • 0: 値なし
      • 1: ひとつ以上のRRI値
  2. 心拍数(uint8)
  3. 心拍数(uint16)
  4. 消費電力(uint16)
  5. RRI(uint16)

心拍数の取得

AppleのCore Bluetoothフレームワークを使って心拍センサから心拍数データを取得する手順

CBCentralManagerを初期化します
selfCBCentralManagerDelegateを継承しています

self.centralManager = CBCentralManager(delegate: self, queue: nil)

CBCentralManagerDelegateによってBluetoothの状態を検知できるので接続可能な場合にscanForPeripheralsを呼んでアドバタイズ中のペリフェラルをスキャンします。
引数のwithServicesに目的のサービスのUUIDを渡すことでそのサービスを持っているペリフェラルを探すことができます。
今回ははHeart Rate ServiceのUUIDを渡します。

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {
    case .poweredOn:
        // スキャン開始
        self.centralManager.scanForPeripherals(withServices: [CBUUID(string: "180D")], options: nil)
    default:
        break
    }
}

ペリフェラルを見つけると以下のメソッドが呼ばれるのでペリフェラルをメニューに追加します。
メニューに追加されたペリフェラルから接続したいものを押下するとconnectActionが呼ばれるようにしています。

// Peripheralが見つかった
func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
    guard let name = peripheral.name else {
        return
    }
    // リストにまだ存在しないペリフェラルをメニューに追加
    if !self.peripherals.keys.contains(name) {
        let item = NSMenuItem()
        item.title = name
        item.action = #selector(connectAction)
        item.state = .off // チェックマークをOFF
        self.menu.insertItem(item, at: self.menu.items.count)
    }
    self.peripherals[name] = peripheral
}

スキャンは明示的に止める必要があるので
connectActionでは最初にスキャンを止めて、接続中のペリフェラルがあれば切断します。
CBCentralManagerconnectメソッドに接続したいペリフェラルを引数に渡すことで接続要求をします。

@IBAction func connectAction(sender: NSButton) {
    self.centralManager.stopScan()
    if self.peripheral != nil {
        self.centralManager.cancelPeripheralConnection(self.peripheral)
    }
    self.peripheral = self.peripherals[sender.title]!

    // メニュー一覧のチェックを外す
    self.menu.items.forEach{ $0.state = .off }
    // ペリフェラルと接続する
    self.centralManager.connect(self.peripheral, options: nil)
    // 接続中のペリフェラルにチェック
    sender.state = .on
}

ペリフェラルと接続が完了すると以下のメソッドが呼ばれるので接続完了したペリフェラルのデリゲートにCBPeripheralDelegateを継承したselfを代入します。
ペリフェラルのdiscoverServicesメソッドにサービスのUUIDを渡してサービスを探します。

// ペリフェラルと接続完了
func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    self.peripheral.delegate = self
    // 目的のServiceを探す
    self.peripheral.discoverServices([CBUUID(string: "180D")])
}

サービスが見つかると以下のメソッドが呼ばれるので今度はキャラクタリスティックを探します。
ここではdiscoverCharacteristicsの引数にはHeart Rate MeasurementのUUIDを渡しています。

// Serviceが見つかった
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
    // 目的のCharacteristicsを探す
    peripheral.discoverCharacteristics([CBUUID(string: "2A37")], for: peripheral.services![0])
}

キャラクタリスティックを見つけたら、キャラクタリスティックが持っているRead/Write/Notify属性によって適切なメソッドを呼んでください。
Heart Rate MeasurementはNotify属性を持っているのでここではsetNotifyValueを呼んで心拍センサーの値を購読します。

// Characteristicが見つかった
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    // Notifyで値を購読する
    peripheral.setNotifyValue(true, for: service.characteristics![0])
}

心拍センサーからデータが通知されると以下のメソッドが呼ばれるのでHeart Rate Measurement Characteristicを参考にデータをパースして心拍数などを取得します。

// 購読中の値が更新された
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
    self.updateWithData(data: characteristic.value! as NSData)
}

func updateWithData(data: NSData) {
    // データ数
    let count = data.length / MemoryLayout<UInt8>.size
    // データ配列
    var dataArray = [UInt8](repeating: 0, count: count)
    // データをUInt8配列にコピー
    data.getBytes(&dataArray, length: count * MemoryLayout<UInt8>.size)
    var offset = 0
    // Flags取得
    let flags = dataArray[offset]
    offset += 1

    var heartRate: UInt16 = 0
    var RRI: [UInt16] = []
    // 心拍数がUInt8の時
    if flags & 0b00001 == 0 {
        heartRate = UInt16(dataArray[offset])
        print("心拍数:",heartRate)
        offset += 1
    } else {// 心拍数がUInt16の時
        // UInt8を連結してUInt16に変換
        let bytes:[UInt8] = [dataArray[offset], dataArray[offset+1]]
        let data = NSData(bytes: bytes, length: 2)
        data.getBytes(&heartRate, length: 2)
        print("心拍数:",heartRate)
        offset += 2
    }
    // 電力消費フィールドがある時
    if (flags&0b01000)>>3 == 1 {
        // 電力消費フィールド分offsetを進める
        offset += 1
    }
    // センサーが接触状態かつRRI値がある場合
    if ((flags&0b00110)>>1 == 3) && ((flags&0b10000)>>4 == 1) {
        // offsetからデータサイズまで2ずつループ(RRIはUInt16のため)
        for i in stride(from: offset, to: dataArray.count, by: 2) {
            // UInt8を連結してUInt16に変換
            let bytes:[UInt8] = Array(dataArray[offset..<i+2])
            let uint16size = MemoryLayout<UInt16>.size
            let data = NSData(bytes: bytes, length: uint16size)
            var u16:UInt16 = 0
            data.getBytes(&u16, length: uint16size)
            RRI.append(u16)
            offset += 2
        }
    }
    for rri in RRI {
        let rriString = String(Double(rri)/1024*1000)
        print("RRI:",rriString)
    }
}

おわりに

Core Bluetoothを使うことで簡単に心拍センサーと接続することができました。
心拍センサはRRIと呼ばれる心電図波形のR波とR波の間隔も取得することができます。
RRIを解析することで交感神経と副交感神経のどちらが優位かなどもわかるので興味があれば調べてください。
心拍センサー以外でもBLE接続可能なものであれば簡単に使うことができるので、是非いろんなセンサーと接続してみてください!

Applibot Advent Calendar 2020」 13日目の記事でした。
明日は@hatayama_abさんです。

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

[Swift] 関数について初歩的なことをまとめる

関数とは

「関数は、入力として引数、出力として戻り値を持つ、名前を持ったひとまとまりの処理」
(石川洋資・西川勇世、『[増補改訂第3版] Swift実践入門 直感的な文法と安全性を兼ね備えた言語』、技術評論社、2020年4月28日 128p)と定義されています。
同じ処理をいろんな箇所で書く時に関数としてまとめて使用します。

書き方

実際の関数の例が以下になります。
doubleが関数名、_が外部引数の省略、xが内部引数、:の後が引数の型、->の後が戻り値の型、{}内が実行される処理になります。
戻り値を返すにはreturn文を{}内で使用します。

func double(_ x:Int) -> Int {
    return x * 2
}

実行してみた

double(2)を実行すると、
内部引数のxに2が入る→{}内の式のxに2が渡される→結果がInt型の戻り値に渡される→4という結果が得られる→定数kekkaに代入
という流れです!

func double(_ x:Int) -> Int {
    return x * 2
}
let kekka = double(2)   //4

引数について

関数の定義時に宣言するもの(上記の例でいうとx)を仮引数
関数の呼び出し時に指定するもの(2)を実引数

参考文献

石川洋資・西川勇世、『[増補改訂第3版] Swift実践入門 直感的な文法と安全性を兼ね備えた言語』、技術評論社、2020年4月28日、453ページ

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

プロパティってなんじゃい?インスタンスって何じゃい?

プロパティについてのメモと、そこから引っ張ってComputedプロパティ(計算型プロパティ)のアクセサの必要性まで。

プロパティとはなんぞや?

クラスや構造体(structで定義するやつ)、列挙体(enumのやつ)で定義することができる定数・変数のこと。

いろいろ種類はあるが今回はこの2つの違いについてから。

格納型プロパティ(Stored property)

struct Example {
  var A: Int
  let B: String
}

インスタンスの一部として定数や変数を格納するプロパティ。

[補足]インスタンスって何じゃい?

オブジェクト、インスタンス、クラスとカタカナ語がごちゃごちゃしますがまとめるとこんな感じです。

IMG_3477.png

オブジェクト指向言語は仮想ワールドにいろんな登場人物(これをオブジェクトとかインスタンスって呼んでる)を登場させて、それぞれにプロパティとか関数(メソッド)とかをくっつけてそれぞれのインスタンス(オブジェクト)の属性や操作を決めていきます。

ちなみにオブジェクトはインスタンスだけではなくて、クラスのこともオブジェクトって呼ぶことがあるそうなのでオブジェクトって出てきたらクラスかインスタンスのどっちかの意味なんだろなと思ってもらえれば良いです。

IMG_3478.png

これをコードで表すと以下。

class teacher {
  let name:String
  var hp:Int

  init(name:String, hp:Int){
    self.name = name
    self.hp = hp

  func teach() ->
  func Shibaku() ->
  func Kireru() ->
  func Suneru() ->
}
}

※func 関数名 以下は適当です。

こんな感じでコードを書いてコンパイルすると、インスタンス(オブジェクト)が爆誕します。

で、話をプロパティに戻していくと、ここのnameやhpが格納型プロパティにあたり、上記のコードでは1番上のコードにはなかったinitと言う謎の文字を追加してありますが、これはイニシャライザと呼ばれ、クラスの属性を初期化しています。これをしないとエラーを出されてしまいます。

計算型プロパティ(Computed property)

特定の値を持っておらず、クラスコードがコンパイルされてインスタンスが生成されるたびに他のプロパティから計算されて値を取得、設定されるプロパティです。

そしてこの「取得」にはgetterを「設定」にはsetterを使います。

具体例をみながら説明していきます。以下のコードを見てください。

class Circle {
  var radius:Double = 1.0
  var area:Double {
    get {
        return radius * radius * Double.pi
      }
    set(menseki) {
        radius = sqrt(menseki / Double.pi)
      }
  }
}

上記のradiusプロパティは格納型プロパティで、areaがgetとset(getterとsetterがあるので)を持つので計算型プロパティとなっている。

ポイントはradiusの1.0のように値が格納されていない点で、以下のようにareaプロパティを呼び出すとgetの内容が実行されて出力される。

let myCircle = Circle()

print("面積\(myCirclearea)")
//面積3.141592653589793

radiusの値を変えておくとgetの中のradiusも変化するのでareaの値も変わる。

let myCircle = Circle()
myCircle.area = 3.0
print("面積\(myCirclearea)")
// 面積28.274333882308138

getterとsetterの存在価値

getterとsetterの存在価値は多く期は3つ。

メリット1:「読むだけしかできない」、「書き込むだけしかできない」フィールド(属性・操作を書く場所)

外部から自由に読めるようにしたいけど、変更されては困ると言うフィールドを作りたい場合に多用されるテクニック。

メリット2:フィールドの名前などクラスの内部設計を自由に変更できる

もし、上のareaをnewAreaに変更したくなった時、getter、setterを使っていないと他のフィールドのコードを全てname→newNameに変えなければならない。

だからget、setを挟むことで他のコードを変えなくて済む。

IMG_3479.png

メリット3:フィールドへのアクセスの検査

setのなかに条件分岐文を用意することで変な値が書き込まれそうになったらエラーを出して弾くことができる。

まとめ

自分の中でモヤモヤしていたものを調べてまとめてみました。

間違った内容などありましたらどしどしご指摘いただければ幸いです。

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

AltSwiftUIさわってみた

iOSDC2020のセッションでAltSwiftUIというライブラリが紹介されました。

SwiftUIは導入するのにビルドターゲットをiOS13以降にする必要がありますが、このライブラリを使えばiOS11からSwiftUIライクにViewを組むことができます。

紹介されたときはこのライブラリが今後どう普及していくのか楽しみだったのですが、iOSDCが終わり数ヶ月経った現在もあまり導入例の記事やリポジトリがなかったため、少し触ってみることにしました。

この記事ではSwiftUIとの全ての違いを網羅することはできませんので、さらに気になられた方はAltSwiftUIのリポジトリをクローンしExampleのプロジェクトを動かしてみることをお勧めします。この記事を読むよりそっちの方がよっぽど勉強になる

導入方法

CocoaPodsで導入することができます。

pod 'AltSwiftUI'

検証環境

iOS 12.0
Xcode12.2
AltSwiftUI 1.3.2

実際にViewを組んでみる

ボタンをタップするとテキストが表示されるだけの単純なviewを組んでみて、実装にどのように差があるかみていきます。

まずこちらがSwiftUIのコードです。あまりSwiftUI自体もキャッチアップできていないため、お作法がなっていないところもあるかもしれませんがご容赦ください?

ContentView.swift
// SwiftUI
import SwiftUI

struct ContentView: View {
    @State private var isPushed = false

    var body: some View {
        VStack(alignment: .center) {
            Image("Image")
                .resizable()
                .scaledToFit()
                .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.width)

            if isPushed {
                Text("Merry Christmas?")
                    .font(.system(size: 20, weight: .bold))
                    .padding(.bottom, 20)
            }

            Button(action: {
                isPushed.toggle()
            }, label: {
                Text("Button")
            })
            .accentColor(.orange)
            .frame(width: UIScreen.main.bounds.width - 24, height: 44)
            .font(.system(size: 14, weight: .bold))
            .overlay(
                RoundedRectangle(cornerRadius: 10)
                    .stroke(Color.orange, lineWidth: 2))
        }
        .offset(x: 0, y: -150)
    }
}

そしてこちらがAltSwiftUIのソースです。
ほぼ同じように実装できているように見えますが多少差があります。

ContentView.swift
// AltSwiftUI
import AltSwiftUI

struct ContentView: View {
    var viewStore = ViewValues()
    @AltSwiftUI.State private var isPushed = false

    var body: View {
        VStack(alignment: .center) {
            Image("Image")
                .resizable()
                .scaledToFit()
                .frame(width: UIScreen.main.bounds.width , height: UIScreen.main.bounds.width)

            if isPushed {
                Text("Merry Christmas?")
                    .font(.system(size: 20, weight: .bold))
                    .padding(.bottom, 20)
            }

            Button(action: {
                isPushed.toggle()
            }, label: {
                Text("Button")
            })
            .accentColor(.orange)
            .border(.orange, width: 2)
            .font(.system(size: 14, weight: .bold))
            .frame(width: UIScreen.main.bounds.width - 24, height: 44)
            .cornerRadius(10)
        }
        .offset(x: 0, y: -150)
    }
}

ではここからはいくつかの違いについて触れていきます。

viewStore

AltSwiftUIはviewStoreの実装を求められます。
求められるものは書いておきましょう!笑

someの有無

SwiftUIではViewの前にsomeをつけたものを準拠させていますが、AltSwiftUIではつける必要がありません。つける必要がないというのは正確ではなく、つけることができないという方が正しいです。

someはSwift5.1から使えるようになったものなので、iOS11をサポートしようとすると使えません。このsomeについては詳しくはこちらの記事に書かれています(あまり理解していないのでどなたか教えてください・・・)

Property Wrapper

AltSwiftUIではSwiftUIのProperty Wrapperも使用できます。使う際は@AltSwiftUI.Stateのように指定する必要があります。最新のリリースをみるとこの指定がなくても直接@Stateで使えるようだったのですが、なぜかエラーで使えませんでした。わかり次第、追記します。

RoundedRectangleが使えなかった

ボタンの角丸を表現するのにSwiftUIではRoundedRectangleを使用したのですが、AltSwiftUIでは使えませんでした。しかしbordercornerRadiusを使えば表現できるので、さほど問題でもないかなと思います。

他をあまり調べていませんが、こういった差はちょこちょこありそうなので使う時に注意が必要かもしれません。

Preview機能は?

SwiftUIというとPreview機能が目玉のひとつでしょう。コードの変更がリアルタイムでViewに反映されるため、ずいぶんと実装が楽になりました。

AltSwiftUIでPreviewを実現するためには、SwiftUIとAltSwiftUIそれぞれのPreviewProviderを準拠させる手法が公式から紹介されています。 実際この方法でPreviewすることができました。

Preview.swift
#if DEBUG && canImport(SwiftUI)
import SwiftUI
import protocol SwiftUI.PreviewProvider
import protocol AltSwiftUI.View

struct MyPreview: AltPreviewProvider, PreviewProvider {
    static var previewView: View {
        ContentView()
    }
}
#endif

まとめと所感

さわってみた感想としては

  • SwiftUIとほぼ同様なシンタックスで宣言的にコードがかけるのが良い
  • SwiftUIに移行する時ラクかも?
  • 予期せぬバグなどは心配なので最小のコンポーネントから試すのがよさそう

と思いました。一方これからSwiftUIが普及していく中で、わざわざAltSwiftUIを使う機会は限定的かな?とも感じました。しかし携わるプロジェクトによってはiOS12以前をサポートしなければならない場面も存在すると思います。そういった方がSwiftUIを実務で取り入れつつ学ぶにはよいかもしれません。

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

CombineのflatMapを書いたらiOS14以上しか使われないって怒られる件について

初投稿です。@dsxsxsxsです。
ここ最近本格的にCombineを使いはじめました。
割としょうもない話をしたいと思います。

怒られる実装

flatMapNoNo.swift
["hoge"].publisher
    .flatMap { _ in
        URLSession.shared.dataTaskPublisher(for: URL(string: "https://qiita.com")!)
    }

上記コードはなぜか上手く行かず、'flatMap(maxPublishers:_:)' is only available in iOS 14.0 or newerっとコンパイラーに怒られる。
そんなわけないですよね。

解決案

upstreamにsetFailureType(to:)を繋いであげれば大丈夫。

flatMapYaYa.swift
["hoge"].publisher
    .setFailureType(to: URLError.self)
    .flatMap { _ in
        URLSession.shared.dataTaskPublisher(for: URL(string: "https://qiita.com")!)
    }

どういうこと?

iOS14以降オンリーのメソッドを呼んでしまったからです。
定義がこうなっています:

flatMapFromNeverFail.swift
@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
extension Publisher where Self.Failure == Never {
    public func flatMap<P>(maxPublishers: Subscribers.Demand = .unlimited, _ transform: @escaping (Self.Output) -> P) -> Publishers.FlatMap<P, Publishers.SetFailureType<Self, P.Failure>> where P : Publisher
}

Failure型がNeverの場合、setFailureType(to:)を内部でやってくれるflatMapが追加されましたという。
ちなみに、switchToLatest()も同様です。?さんはいつも通り、さり気なくサプライズを用意してくれます。

switchToLatestFromNeverFail.swift
@available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *)
extension Publisher where Self.Failure == Never, Self.Output : Publisher {
    public func switchToLatest() -> Publishers.SwitchToLatest<Self.Output, Publishers.SetFailureType<Self, Self.Output.Failure>>
}

どの人たちがよくハマるの?

  • XCode12から始めた人
  • Rxに慣れた人
  • サポートOSバージョンをiOS13指定しまっている人

僕は見事に上記3点フルコンボでした。?

どの人たちがハマらないの?

  • 去年からCombineを始めたトップバッター勢
  • そもそもiOS13を切った勢

参考した記事

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

Swiftにおける型の構成要素〜プロパティ前編〜

プロパティ

プロパティとは、型に紐づいた値のことで型が表すものの属性の表現などに使用されます。

車を例にしますと、車は車種やタイヤ、エンジンなどの属性を持っています。

これを型とプロパティの関係で表すと、
車という型は、車種やタイヤなどのプロパティを持っているということになります。

車種は車によって異なりますので、
プロパティは型のインスタンスに紐づいた変数や定数であるとも言えます。

定義方法

プロパティはvarキーワードもしくはletキーワードで定義します。

変数や定数と同様に、varキーワードは再代入可能なプロパティを定義し、
letキーワードは再代入が不可能なプロパティを定義します。

定義する際のコードは下記のようになります。

struct 構造体名 {
    var プロパティ名 プロパティの型の値 =    // 再代入可能なプロパティ
    let プロパティ名 プロパティの型の値 =    // 再代入不可能なプロパティ
}

プロパティにアクセスするには、型のインスタンスが代入された変数や定数に
.(ドット)プロパティ名をつけて変数名.プロパティ名のように記述します。

私が将来乗りたい車であるレクサスを例にしてコードを書いてみます。

let myCar = Car()で、定数myCarに対してCar型の構造体をインスタンス化しています。

struct内で定義された定数nameに対してアクセスするためには、
変数名.プロパティ名の形で記述するのでmyCar.nameという形になります。

struct Car {
    let name = "LEXUS LS"   // 車名
    let price = 9000000   // 値段
    var mileage = 20000   // 走行距離
}

let myCar = Car()
print(myCar.name)
print(myCar.mileage)

実行結果
LEXUS LS
20000

プロパティの型の整合性を保つために、
インスタンス化の完了までに全てのプロパティに値が代入(=初期化)されてなければいけません。

初期化されていないプロパティが存在するとコンパイルエラーが発生します。

したがって、全てのプロパティは、宣言時に初期値を持っているか、
イニシャライザ内で初期化されるかのいずれかの方法で値を持つ必要があります。

(イニシャライザに関しては別の記事に記載してあります。)

プロパティの分類

プロパティは、紐付く対象により2種類のプロパティに分類することができます。

前者のプロパティの方が主流ですが、
ぜひ両方とも覚えておきましょう!

インスタンスプロパティ

インスタンスプロパティとは、型のインスタンスに紐付くプロパティになります。

インスタンスに紐付くため、
インスタンスごとに異なる値を持たせることができるのが特徴です。

単にプロパティと呼ぶ場合はこちらを表すことが多い気がします。

varやletで定義したプロパティはデフォルトではインスタンスプロパティとなります。
なので、先ほどの車の例で出たプロパティは全てインスタンスプロパティです。

下記の例では、同じ型を使ってインスタンス化しても、
異なるインスタンスになるので異なった値を持たせることができます。

struct human {
    var name = "Aoki"
    var age = 11
}

let human1 = human()
var human2 = human()
human2.name = "Tanaka"

print(human1.name)
print(human2.name)

実行結果
Aoki
Tanaka

スタティックプロパティ

スタティックプロパティは型のインスタンスではなく型自身に紐付くプロパティで、
インスタンス間で共通する値の保持などに役立ちます。

スタティックプロパティの定義方法は簡単で、
プロパティの宣言時に先頭にstaticキーワードを追加するだけです。

スタティックプロパティは他のプロパティとは異なり、
型名.スタティックプロパティ名で値を参照することができます。

例として、メールに関する構造体Mailを作成してみました。

宛先は毎回同じでいいので、static let fromのように
スタティックプロパティで定義しております。

このスタティクプロパティにアクセスする際にはMail.fromでアクセスします。

また、func mailer(mail: Mail) { }のように、
引数の型を今回作成したMail型にしていますので、
let mail1 = Mail()でインスタンス化したMail型のmail1を引数に渡すことができます。

struct Mail {
    static let from = "Tanaka for iPhone"
    var to = "Matsumoto"
    var body = "Hello!"
}

func mailer(mail: Mail) {
    print(Mail.from)
    print("To:\(mail.to)")
    print(mail.body)
}

let mail1 = Mail()
var mail2 = Mail()
mail2.to = "Okada"
mail2.body = "See you Okada"

mailer(mail: mail1)
print("---")
mailer(mail: mail2)

実行結果
Tanaka for iPhone
To:Matsumoto
Hello!
---
Tanaka for iPhone
To:Okada
See you Okada

プロパティ前編は以上になります。

結構長くなりそうなので、前編と後編に分けました!
後編の方が大切な内容が書かれているのでぜひご覧ください。

後編はコチラ
-> Swiftにおける型の構成要素〜プロパティ後編〜

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

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

Swiftにおける型の構成要素〜定義方法〜

私たちが当たり前のように使っているInt型やString型ですが、
もちろんどこかで事前に定義されています。

プロジェクトを作成する際に、その定義ファイルを読み込んでいるので
当たり前のようにいろいろな型を使うことができるようになっているのです。

様々な型が存在するわけですが、
その型はどのように定義されているの?って話になってきます。

型の定義方法は、
・クラス
・構造体
・列挙型

のどれかで定義されています。

そして、クラス・構造体・列挙型の3つには、
メソッドやプロパティなどの共通の要素が用意されています。

今回は、型に共通する構成要素について説明していきます。
クラス、構造体、列挙型については後日別の記事で説明する予定です。

型に共通するもの

Int型やString型などの、代表的な型を構成する要素は、
型が持つ値を保存するプロパティと、型の振る舞いを表すメソッドの2つです。

プロパティは、型に紐付いた定数や変数と言い換えることができ、
メソッドは、型に紐付いた関数と言い換えることができます。

この2つに加えて、型を構成する要素には、
初期化を行うイニシャライザ、コレクションの要素を取得するサブスクリプト
型内に型を定義するネスト型などが存在します。

型の基本

型についての基本知識として、定義方法とインスタンス化方法について説明します。

インスタンスとは、型を実体化したものであり、
型に定義されているプロパティやメソッドを持ちます。

つまり、String型の値"aaa"を定義した際、
aaaはString型のインスタンスであり、countプロパティやappend( )メソッドを持ちます。

関数と言ったりメソッドと言ったり、
ごちゃごちゃになるかもしれませんが、関数≒メソッドです。

呼び方が違うだけで構成しているものは同じです。

Int型に定義されている関数を、
Int型が持つメソッドと言ったりします。

定義方法

構造体はstructキーワード
クラスはclassキーワード
列挙型はenumキーワード
 を使って型を定義します。

struct 構造体名 {
   構造体の定義
}

class クラス名 {
   クラスの定義
}

enum 列挙型名 {
   列挙型の定義
}

型を構成する要素であるプロパティやメソッドは、
全て{ }内に記述していきます。

インスタンス化の方法

型をインスタンス化するには、型名に( )をつけてイニシャライザを呼びます。
必要に応じて( )内に引数を渡します。

型の定義とインスタンス化を合わせて行います。

// 構造体の定義
struct sample{ }

// 構造体sampleのインスタンス化
let instance = sample()

最低限の定義方法については以上になります!

この他にも、型を定義するにあたり様々な要素が必要になるので
それらについても記事にしましたので、是非ご覧ください。

・Swiftにおける型の構成要素〜プロパティ前編〜
・Swiftにおける型の構成要素〜メソッド〜
・Swiftにおける型の構成要素〜イニシャライザ〜
・Swiftにおける型の構成要素〜サブスクリプト〜
・Swiftにおける型の構成要素〜エクステンション〜
・Swiftにおける型の構成要素〜型のネスト〜

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

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

Swiftにおける型の構成要素〜型の基本〜

私たちが当たり前のように使っているInt型やString型ですが、
もちろんどこかで事前に定義されています。

プロジェクトを作成する際に、その定義ファイルを読み込んでいるので
当たり前のようにいろいろな型を使うことができるようになっているのです。

様々な型が存在するわけですが、
その型はどのように定義されているの?って話になってきます。

型の定義方法は、
・クラス
・構造体
・列挙型

のどれかで定義されています。

そして、クラス・構造体・列挙型の3つには、
メソッドやプロパティなどの共通の要素が用意されています。

今回は、型に共通する構成要素について説明していきます。
クラス、構造体、列挙型については後日別の記事で説明する予定です。

型に共通するもの

Int型やString型などの、代表的な型を構成する要素は、
型が持つ値を保存するプロパティと、型の振る舞いを表すメソッドの2つです。

プロパティは、型に紐付いた定数や変数と言い換えることができ、
メソッドは、型に紐付いた関数と言い換えることができます。

この2つに加えて、型を構成する要素には、
初期化を行うイニシャライザ、コレクションの要素を取得するサブスクリプト
型内に型を定義するネスト型などが存在します。

型の基本

型についての基本知識として、定義方法とインスタンス化方法について説明します。

インスタンスとは、型を実体化したものであり、
型に定義されているプロパティやメソッドを持ちます。

つまり、String型の値"aaa"を定義した際、
aaaはString型のインスタンスであり、countプロパティやappend( )メソッドを持ちます。

関数と言ったりメソッドと言ったり、
ごちゃごちゃになるかもしれませんが、関数≒メソッドです。

呼び方が違うだけで構成しているものは同じです。

Int型に定義されている関数を、
Int型が持つメソッドと言ったりします。

定義方法

構造体はstructキーワード
クラスはclassキーワード
列挙型はenumキーワード
 を使って型を定義します。

struct 構造体名 {
   構造体の定義
}

class クラス名 {
   クラスの定義
}

enum 列挙型名 {
   列挙型の定義
}

型を構成する要素であるプロパティやメソッドは、
全て{ }内に記述していきます。

インスタンス化の方法

型をインスタンス化するには、型名に( )をつけてイニシャライザを呼びます。
必要に応じて( )内に引数を渡します。

型の定義とインスタンス化を合わせて行います。

// 構造体の定義
struct sample{ }

// 構造体sampleのインスタンス化
let instance = sample()

最低限の定義方法については以上になります!

この他にも、型を定義するにあたり様々な要素が必要になるので
それらについても記事にしましたので、是非ご覧ください。

・Swiftにおける型の構成要素〜プロパティ前編〜
・Swiftにおける型の構成要素〜メソッド〜
・Swiftにおける型の構成要素〜イニシャライザ〜
・Swiftにおける型の構成要素〜サブスクリプト〜
・Swiftにおける型の構成要素〜エクステンション〜
・Swiftにおける型の構成要素〜型のネスト〜

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

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

【備忘録】Swift 実践入門について 第10章

Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、
今は必要ない、と言い聞かせて飛ばしています。

第10章
ジェネリスク IntやStringといった型を自分で作ることができる。
理解度:%くらいか

関数や型にとつけることで型Tを作れる。Tがよく使われているが、任意の文字でOK。
func Equal(_ x : T, _ y : T) -> Bool {
return x == y
}

Equal(“abc”, “abc”) //true
Equal(1.01, 1.02) //false
Eual(true, true) //true

<>の中の型名と、()の中の型名を一致させることで様々な型を柔軟に使うことができる。

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

UINavigationControllerをSceneDelegateでRootViewにセットする時の注意点!

この記事で言いたいこと

すごく初歩的ですが、
Storyboardを使っているのか、使っていないのかちゃんと意識しましょう!
著者はこれで2時間無駄にしました!
下記の?‍♂️これはダメです!を実施すると、当然のことながら、IBOutletがnilになります。
よって、ViewDidLoad(){ }でUIに値を入れようとしてもクラッシュします!

環境

  • Xcode : Version 12.2
  • Swift5

Storyboardを使う場合

//Storyboardを使用している場合は、StoryboardからViewControllerを生成しましょう。
      //?‍♂️これはOK! 
        let storyboard = UIStoryboard(name: "Main", bundle: .main)
        let vc = storyboard.instantiateInitialViewController() as! ViewController

      //?‍♂️これはダメです!  let vc = ViewController()
class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        let storyboard = UIStoryboard(name: "Main", bundle: .main)
        let vc = storyboard.instantiateInitialViewController() as! ViewController
        let navigation = UINavigationController(rootViewController: vc)
        window.rootViewController = navigation
        self.window = window
        window.makeKeyAndVisible()

    }
}

Storyboardを使わない場合

1.Main.storyboardを削除。
2.プロジェクトのTARGETS > InfoにあるMain storyboard file base nameという項目を削除。
Screen Shot 2020-12-12 at 1.32.31 PM.png

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        let vc = ViewController()
        let navigation = UINavigationController(rootViewController: vc)
        window.rootViewController = navigation
        self.window = window
        window.makeKeyAndVisible()

    }
}

参照

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

realmデータベースを可視化

直面したもの

realmbrowserを使おうと思ったら、なんか暗号化キーを入れなきゃ使えなかった。

エラー内容

please enter a valid encryption key for this realm file

解決策

https://github.com/realm/realm-cocoa/issues/4795

realm Studioに可視化ソフトが変わったのかなという印象。
Realm studioをインストールして、default.realmファイルをrealm studioで開いたら解決しました

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

【備忘録】Swift 実践入門について 第9章

Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、
今は必要ない、と言い聞かせて飛ばしています。

第9章
プロトコル 仕様書のこと。その仕様書を適合したら、プロトコル内部のプロパティやメソッドは全部盛り込む必要があるため確認がしやすい。
理解度:40%くらいか

struct / class / enumが設計図、インスタンスが出来上がった実体だとすると、プロトコルは仕様書。可読性を高める効果がある。

9-3 プロトコルを構成する要素
プロトコル内ではletは使えないのでvarを使う。var A : String { get set }のような感じ。このgetとget setがよく分かってない。コンピューターサイエンスでお馴染みだからか、swiftで改めて丁寧な説明はないように感じた。まあgetはプロパティの取得(get)、get setでプロパティを取得し、具体的な値を代入(set)までするというイメージ。

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

[Swift5]秒数を指定して数秒遅れて処理を実行する方法。(遅延処理)

遅延処理とは

画面遷移のタイミングなど少し遅らせて処理を実行したいときがアプリケーションには存在すると思います。そんな時に役立つのが遅延処理です

やり方は簡単です。

実装方法

ViewController.swift
//{ }中に処理を記述すると指定した秒数遅れて処理される
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
  //処理したいコードを記述
}

参考にしてください!

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

AVSpeechSynthesizerを使って、textFieldの文字を読み上げる

はじめに

この記事はLife is Tech!Members Advent Calendar 2020 の12日目の記事です。
初めて記事を書くので至らない点もあると思いますが、よろしくお願いします。

環境

xcode12.3
swift 5.0以降

AVSpeechSynthesizerとは

まず、今回の記事で使用するAVSpeechSynthesizerについて説明します。
こちらのページによると、

テキストの発話から合成音声を生成し、進行中の音声の監視または制御を可能にするオブジェクト。

とあります。簡単にいうと、テキストを読み上げてくれるよーということですね。

コード

SpeechService.swift
import UIKit
import AVFoundation //①

class SpeechService {
    private let synthesizer = AVSpeechSynthesizer()
    var rate: Float = AVSpeechUtteranceDefaultSpeechRate
    var voice = AVSpeechSynthesisVoice(language: "ja_JP") //②

    func say(_ phrase: String) {
        let utterance = AVSpeechUtterance(string: phrase) //③
        utterance.rate = rate
        utterance.voice = voice
        utterance.pitchMultiplier = 1.0 //④

        synthesizer.speak(utterance) //⑤
    }

    //⑥
    func stop() {
        synthesizer.stopSpeaking(at: AVSpeechBoundary.immediate)
    }
}

ViewController.swift
import UIKit
import AVFoundation //⑦

class ViewController: UIViewController{

    let speechService = SpeechService()

    @IBOutlet weak var textField: UITextField!

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

    @IBAction func tappedSpeakButton(_ sender: Any) {
        speechService.say(textField.text!) //⑧
    }

    @IBAction func tappedStopButton(_ sender: Any) {
        speechService.stop() //⑨
    }

}


コード解説

①AVFoundationをインポートしてあげてください。これによって、AVSpeechSynthesizerを使うことができます。
②言語を設定します。今回は日本語の文章を読むことを前提としているので、languageはja_JPです。英語の場合は、en_JPかen_USを入力してください。
③読み上げる文字を設定します。
④声の高さを調節します。0に近づく程、音が低くなります。
 私の場合、1.0だと少し高いと思ったので、0.8などに設定して使っています。
⑤実際に話すところです。
⑥話すのを止めます。
⑦①と同じで、AVFoundationをインポートします。
⑧ボタンをタップすると、textFieldの内容を話します。
⑨ボタンをタップすると、読み上げを停止します。
スクリーンショット 2020-12-11 19.03.59.png

storyboardで、textField、tappedSpeakButton、tappedStopButtonを関連付けして完成です。

最後に

最後まで読んでいただき、ありがとうございました!
間違いだったり、わかりにくい点などありましたら、コメント欄にてご指摘ください。

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

Realmの「逆方向の関連」を利用してTodoリストを作る

TableViewの勉強を進めていると、Sectionが複数になった途端コードが散らかってしまいませんか?
2次元配列にしたり、Sectionクラスを作ってプロパティとしてrow要素を組み込んだり方法は色々ありますが、Realm の逆方向の関連を使うと比較的綺麗にまとまったので紹介します。

Sectionとrowだけでなく、一対多のネスト構造のもの全般に活躍できそうです。例えば、メインオブジェクトはゲームソフトだけど、新規作成はゲームの記録をメインとしたUIを想定している場合でも、Realmの逆方向の関連を活用すればUIに沿った形でコードが作成できるかと思います。

今回は、Realmの逆方向の関連を用いて、簡単なTodoアプリの新規作成機能を作りたいと思います。

環境 
Swift 5.2
Xcode 11.4
Realm 10.4.0

Realmのインストールが済んだ状態から進めます。

todo.swift
import FonDation
import RealmSwift

class Section: Object {
    @objc dynamic var name: String = ""
    //Taskオブジェクトとの逆方向の関連を明示
    let tasks = LinkingObjects(fromType: Task.self, property: "section")
}

class Task: Object {
    @objc dynamic var section: Section?
    @objc dynamic var name: String = ""
}

sectionに格納されるSectionクラスと、rowに格納されるTaskクラスは、一対多の関係なので、SectionクラスのプロパティにTaskをネストさせるのが自然です。が、どちらかというとSectionよりもTaskをメインオブジェクトとして扱いたいのと、後々検索やソートをかける時などTaskにSectionの情報が直接入っている方が便利な場合も多いため、TaskのプロパティにSectionを入れています。
新しく作ったTaskのsectionプロパティが既存のSectionオブジェクトと一致すると、自動でその配下に入る仕組みにより、SectionクラスとTaskクラスの一対多のネスト関係が成り立っています。これが逆方向の関連の強みの一つだと思います。

新しく作ったTaskオブジェクトのsectionプロパティが既存のSectionオブジェクトと一致した場合に、自動でその配下へ追加される仕組みです。ViewControllerは例えばこのような感じになります。

viewController.swift
import UIKit
import RealmSwift

class ViewController: UIViewController {


    @IBOutlet weak var tableView: UITableView!

    //既存のSectionを間違いなく選択できるようにする
    var pickerView: UIPickerView = UIPickerView()
    //テーブルビューの元の配列
    var list: Results<Section>?
    //Realmオブジェクト作成
    let realm = try!Realm()

    //タスク名を入れるテキストフィールド
    @IBOutlet weak var taskTextField: UITextField!

    //セクション名を入れるテキストフィールド
    @IBOutlet weak var SectionTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        //Sectionオブジェクトを全て呼び出しlist配列に格納
        list = realm.objects(Section.self)

        setUpTable()
        callPickerView()
    }

    //追加ボタン
    @IBAction func addButton(_ sender: Any) {

        do {
            try realm.write{
                //セクションのテキストフィールドの文字を元にRealmから呼び出す
                let section = realm.objects(Section.self).filter("name = '\(SectionTextField.text ?? "")'").first
                //タスクの作成、呼び出したsectionを格納、なければ新しくインスタンス作成
                let task = Task(value: ["name": taskTextField.text ?? "不明",
                                    "section": section ?? Section(value: ["name": SectionTextField.text ?? "不明"])])
                //新規タスクを追加
                realm.add(task)
            }
        }catch {

        }
        tableView.reloadData()
    }


}

extension ViewController {

    //tableViewのセットアップ
    func setUpTable(){
        tableView.delegate = self
        tableView.dataSource = self
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "taskCell")
        tableView.reloadData()
    }

    //SectionのテキストフィールドをタップするとpickerViewが呼び出される
    func callPickerView() {
        pickerView.delegate = self
        pickerView.dataSource = self
        let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 50))
        let item1 = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(done))
        let flexibleItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
        toolbar.setItems([item1, flexibleItem], animated: true)

        SectionTextField.inputView = pickerView
        SectionTextField.inputAccessoryView = toolbar
    }

    //PickerViewの完了ボタンをタップした時の処理
    @objc func done() {
        if let list = list {
            SectionTextField.text = "\(list[pickerView.selectedRow(inComponent: 0)].name)"}
        else {
            return
        }
        SectionTextField.endEditing(true)
    }
}

extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

            return list?[section].tasks.count ?? 0

    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       // guard let count = timeCardData?.count, indexPath.row < count else { return cell }
        let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath)
        cell.textLabel?.text = list?[indexPath.section].tasks[indexPath.row].name
        return cell
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        guard let list = list else {
            return "no Section"
        }
        return list[section].name
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return list?.count ?? 0
    }
}

extension ViewController: UITableViewDelegate {

}

extension ViewController: UIPickerViewDelegate {
    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return list?.count ?? 0
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
        return list?[row].name ?? ""
    }
}

extension ViewController: UIPickerViewDataSource  {
    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 1
    }
}


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