dasukoの技術ブログ

現役エンジニアのブログです。

【SwiftUI】TextFieldの使い方

はじめに

この記事ではSwiftUIのTextFieldの使い方について説明します。

TextFieldはその名の通り読み書き可能(編集可能)なテキストを出力します。

  TextField には文字列のバインドを指定し、ユーザがテキストフィールド内のテキストを編集する度に

文字列の変更通知を受け取ることができます。

 

ちなみにSwiftUIの導入方法についてはこちらで説明しています。

dasuko.hatenadiary.jp

    VStackHStackについてはこちらを見てみてください!

dasuko.hatenadiary.jp

単純な実装

ラベルと文字列へのバインドだけを渡すシンプルなテキストフィールドを使ってみます。

import SwiftUI

struct ContentView: View {
    @State private var userName = ""
    
    var body: some View {
        VStack {
            TextField("UserName", text: $userName)
            Text(userName)
        }
    }
}

 

↓プレビューはこんな感じになります。

gyazo.com

テキストの変更通知を受け取る

以下は3つ目の引数には編集中通知を受け取った時に変更通知を受け取り、

編集中かどうかでテキストの色を変更するサンプルです。

今回はわかりやすいようにuserNamemailに初期値を設定し、それぞれのTextFieldを配置してみました。

import SwiftUI

struct ContentView: View {
    @State private var userName = "User"
    @State private var mail = "Mail"
    
    @State private var isEditingUserName = false
    @State private var isEditingMail = false

    var body: some View {
        VStack {
            TextField(
                "UserName",
                text: $userName)
            { isEditing in
                self.isEditingUserName = isEditing
            } onCommit: {
                validate(name: userName)
            }

            TextField(
                "Mail",
                text: $mail)
            { isEditing in
                self.isEditingMail = isEditing
            } onCommit: {
                validate(name: userName)
            }

            Text(userName)
                .foregroundColor(isEditingUserName ? .red : .blue)
        }
    }
    
    func validate(name: String) {
        // validate
    }
}

 

↓プレビューはこんな感じになります。

他のViewにフォーカスがうつったときにisEditingfalseになっているのが分かりますね。

gyazo.com

 

Modfier

disableAutocorrection

自動修正を無効にするかどうかを指定します。

無効にした場合は、キーボードが自動修正システムが入力を提案したり、上書きしようとしたりしません。

import SwiftUI

struct ContentView: View {
    @State private var userName = "User"
    @State private var isEditing = false

    var body: some View {
        VStack {
            TextField(
                "UserName",
                text: $userName)
            { isEditing in
                self.isEditing = isEditing
            } onCommit: {
                validate(name: userName)
            }
            .disableAutocorrection(true)

            Text(userName)
        }
    }
}

border

テキストフィールドの枠線を指定できます。

以下の例ではテキストフィールドの枠線を青色に指定しています。

import SwiftUI

struct ContentView: View {
    @State private var userName = ""
    @State private var isEditing = false

    var body: some View {
        VStack {
            TextField(
                "UserName",
                text: $userName)
            { isEditing in
                self.isEditing = isEditing
            } onCommit: {
                validate(name: userName)
            }
            .border(Color.blue)
        }
    }
    
    func validate(name: String) {
        // validate
    }
}

  ↓プレビューはこんな感じになります。

f:id:dasuko:20210302225847p:plain

keyboardType

キーボードタイプを指定します。

keyboardTypeには以下のような定義があります。

名前 説明
default デフォルトのキーボード
asciiCapable ASCII文字を表示するキーボード
numbersAndPunctuation 数字と句読点を表示するキーボード
URL URL入力用のキーボード
numberPad PIN入力用のテンキー
phonePad 電話番号入力用のキーパッド
namePhonePad 人の名前または電話番号入力のためのキーパッド
emailAddress メールアドレス入力用キーボード
decimalPad 数字と小数点付きキーボード
twitter Twitterテキスト入力用キーボード(@や#に簡単にアクセスできる)
webSearch Web検索用語とURL入力用キーボード
asciiCapableNumberPad ASCII数字のみを出力するテンキー
alphabet アルファベット入力用キーボード

  以下の例ではキーボードタイプにURLを指定しています。

import SwiftUI

struct ContentView: View {
    @State private var url = ""
    @State private var isEditing = false

    var body: some View {
        VStack {
            TextField(
                "URL",
                text: $url)
            { isEditing in
                self.isEditing = isEditing
            } onCommit: {
                validate(url: url)
            }
            .keyboardType(.URL)
        }
    }
    
    func validate(url: String) {
        // validate
    }
}

autocapitalization

自動大文字化するかどうかを設定します。

引数にはUITextAutocapitalizationTypeを指定します。

UITextAutocapitalizationTypeには以下のようなタイプがあります。

プロパティ 説明
text 現在のサーチバーに入力されているテキスト
placeholder テキストフィールドに何も入力していないときに表示される文字列
searchBarStyle 検索バーのスタイル
autocapitalizationType テキストの自動大文字化するかどうかの制御

 

autocapitalizationTypewordsを指定すると、自動的に各単語の最初の文字が大文字化されます。

sentencesを指定すると、各文毎に、先頭が大文字化されます。

noneを指定すると、自動的に大文字化されません。

 

以下の例ではautocapitalizationwordsを指定しています。

import SwiftUI

struct ContentView: View {
    @State private var name = ""
    @State private var isEditing = false

    var body: some View {
        VStack {
            TextField(
                "Name",
                text: $name)
            { isEditing in
                self.isEditing = isEditing
            } onCommit: {
                validate(name: name)
            }
            .autocapitalization(.words)
        }
    }
    
    func validate(name: String) {
        // validate
    }
}

 

↓プレビューはこんな感じです。

words指定しているので、最初の文字は大文字になります。

gyazo.com

textContentType

テキストフィールドの入力領域ののコンテンツタイプを指定します。

引数にはUITextContentTypeを指定します。

UITextContentTypeの定義には以下のようなものがあります。

名前 期待される入力
URL URL
addressCity 都市名
addressCityAndState 州名と都市名
addressState 状態名
countryName 国または地域名
creditCardNumber クレジットカード番号
emailAddress 電子メールアドレス
familyName 苗字
fullStreetAddress 場所を完全に識別する番地
givenName 名前(ファーストネーム
jobTitle 職業名
location 場所、住所
middleName ミドルネーム
name 名前
namePrefix Drなどのプレフィックス
nameSuffix Jrなどのサフィックス
nickname ニックネーム
organizationName 組織名
postalCode 郵便番号
streetAddressLine1 番地の最初の行
streetAddressLine2 番地の2行目
sublocality サブローカリティ。自治体名
telephoneNumber 電話番号
username アカウントまたはログイン名
password パスワード
newPassword 新しいパスワード
oneTimeCode ワンタイムコード

UITextContentTypeを指定してもビューに特に変化はありません。

UITextContentTypeを指定する場合は、だいたいキーボードタイプも指定します。

 

multilineTextAlignment

真ん中に寄せたり、右に寄せたりなど、テキストの配置を指定します。

import SwiftUI

struct ContentView: View {
    @State private var name = ""
    @State private var isEditing = false

    var body: some View {
        VStack {
            TextField(
                "Name",
                text: $name)
            { isEditing in
                self.isEditing = isEditing
            } onCommit: {
                validate(name: name)
            }
            .multilineTextAlignment(.center)
        }
    }
    
    func validate(name: String) {
        // validate
    }
}

 

↓真ん中に指定するとこんな感じです。

f:id:dasuko:20210302233712p:plain

最後に

TextFieldでは複数行の入力を受け付けることができないので、

複数行の入力を受付けたい場合はTextEditorを使います!  

 

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

SwiftUIを一から極めたいという人は本を買ってみるといいかも!

SwiftUIを本気でやろうとしていない人にはお勧めしません。

 

 

参考

Apple Developer Documentation

【Swift】Swiftでコルーチン使ってみた(SwiftCoroutine)

はじめに

今回はSwiftでSwiftCoroutineというライブラリでCoroutineを使ってみたいと思います!

ライブラリのリポジトリはこちらです!

github.com

iOSmacOSなどでの動作を保証する最初のコルーチンライブラリみたいですね!

コルーチンとは

サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。 Wikipediaより引用

コルーチン - Wikipedia

要するにasync/awaitパターンを使って処理を中断したり、再開したり、ある処理が終了するまで待機するといったことを実装するためのプログラミング構造です^ ^

SwiftCoroutineの特徴

  • ロックフリー(ロック不要)
  • スレッドをブロックすることなく待機処理が実装できる
  • パフォーマンスに優れ、かなり高速

プロジェクトへ導入

今回はCocoaPodsを使って導入してみます。

プロジェクトフォルダ内で

pod initを実行します。

 

Podfile内に↓を追加します。

  pod 'SwiftCoroutine'

 

pod installします。

*.xcworkspaceが出力されるので、それを開きます!!!!

※CocoaPodsの使い方については、時間があったら記事書きます!

使ってみる

まずはインポートですね!

以下を追加するとインポートできます!

import SwiftCoroutine

Coroutine

↓これでメインスレッドでコルーチンを実行できるみたいです!

DispatchQueue.main.startCoroutine {
}

 

まずはコルーチンの中でURLSessionを使ってみました!

DataResponseを取得し、responsedataを取得してみました!

import SwiftCoroutine

...

    func doSomething() {
        print("start")
        DispatchQueue.main.startCoroutine {
            
            let future = URLSession.shared.dataTaskFuture(for: URL)
            
            let dataResponse = try future.await()
            let response = dataResponse.response
            let data = dataResponse.data
            
            print("finish")
        }
    }

結果は・・・・

responsedataも正常に取得できました!

ちゃんと待機できてますね!

 

時間も含めたログはこんな感じです。

リクエストに1秒かかっていて、完了を待機できていることが分かりますね

14:05:33 start
14:05:34 finish

 

Future 

続いてもう少し、待機していることがわかりやすいサンプルを実行してみたいと思います!

以下のような関数を定義して実行してみました!

CoFutureは待機ができるオブジェクトで、ジェネリクスで引数の型を指定しています

[CoFuture].await()でfutureが終了するまでコルーチン内で待機できます

今回作成したfutureは5秒後に1を返すfutureです!

 func doSomething() {
        let future: CoFuture<Int> = DispatchQueue.global().coroutineFuture {
            try Coroutine.delay(.seconds(5))
            return 1
        }
        
        DispatchQueue.main.startCoroutine {
            let result: Int = try future.await()
            
            print("result:\(result)")
        }
        print("finished.")
    }

理想としては、コルーチンの中で5秒待機するので、

finishというログが出力され、その5秒後にresult:1が出力されてほしいですね

 

結果は・・・・

14:34:27: finished.
14:34:32: result:1

 

期待通り、finishが出力された5秒後にresult:1が出力されました^ - ^

SwiftCoroutineめっちゃ便利(^○^)

 

ちなみにロック不要なのでこんな処理も簡単に実装できちゃいます!

以下のサンプルでは二つのfuture(future1とfuture2)の合計を出力しています。

future1はglobal queue上で実行され、5秒待機後に1を返します。

future2はmain queue上で実行され、3秒待機後に2を返します。

これら二つのfuture(タスク)を並列に実行してみます!

 func doSomething() {
        let future1: CoFuture<Int> = DispatchQueue.global().coroutineFuture {
            try Coroutine.delay(.seconds(5))
            return 1
        }

        let future2: CoFuture<Int> = DispatchQueue.main.coroutineFuture {
            try Coroutine.delay(.seconds(3))
            return 2
        }

        DispatchQueue.main.startCoroutine {
            let result: Int = try future1.await() + future2.await()
            
            print("result:\(result)")
        }
        print("finished.")
    }

期待される結果はどうなるでしょうか?

future1とfuture2のうち、待機時間が長いのは5秒待機するfuture1なので、

finishedというログが出力されてから5秒後にresult:3というログが出力されてほしいですね

 

結果は・・・

14:40:22 +0000: finished.
14:40:27 +0000: result:3

 

期待通りfinished.のログが出力されてから5秒後にresult:3というログが出力されました(^○^)

Promise

Promiseを使うことで待機後に値を返却することができます。

次のサンプルでは、makeIntFuture関数でPromise(CoPromise)を使って1秒後に10を返しています。

    func makeIntFuture() -> CoFuture<Int> {
        let promise = CoPromise<Int>()
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            promise.success(10)
        }
        
        return promise
    }

    func doSomething() {
        DispatchQueue.main.startCoroutine {
            let result = try self.makeIntFuture().await()
            
            print("\(Date()): result:\(result)")
        }
        print("\(Date()): finished.")
    }

 

期待される結果としては、finishedというログが出力された1秒後にreuslt:10と出力されることです。

結果は・・・

 

14:50:29: finished.
14:50:30: result:10

 

期待通りfinishedというログが出力された1秒後にreuslt:10と出力されました!

 

 

FuturePromiseを使うことで簡単にコルーチン間で値をやり取りすることができます!

最後に

他にもChannelScopeといった機能がありますが、今回は割愛します。

いかがだったでしょうか?  

SwiftCoroutineはかなり便利だと思います^ ^

ぜひ使ってみてください!

 

 

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

Swiftを一から極めたいという人は本を買ってみるといいかも!

Swiftを本気でやろうとしていない人にはお勧めしません。

 

 

参考

GitHub - belozierov/SwiftCoroutine: Swift coroutines for iOS, macOS and Linux.

コルーチン - Wikipedia

【SwiftUI】Buttonの使い方

はじめに

今回はSwiftUIのButtonの使い方について説明します。

Buttonはその名の通りボタンを表示します。

struct Button<Label> where Label : View

 

 

ちなみにSwiftUIの導入方法についてはこちらで説明しています。

dasuko.hatenadiary.jp

 

テキストのAlignmentについてはこちらで解説しています。

dasuko.hatenadiary.jp

  VStackHStackについてはこちらを見てみてください!

dasuko.hatenadiary.jp

簡単な使い方

第一引数にラベルを指定して、第二引数にボタンが押されたときのアクションを指定してボタンを作成してみます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button") {
            print("Button was pressed.")
        }
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301213002p:plain

 

ボタンを押したときのアクションは、こんな感じでメソッドを指定することもできます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: buttonPressed)
    }
    
    func buttonPressed() {
        print("Button was pressed")
    }
}

Buttonにはテキストだけでなく、labelを指定することも可能です。

以下のようにImageTextを適用してみます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(action: {
            print("Button was pressed")
        }, label: {
            Image(systemName: "play")
            Text("Play")
        })
    }
}

 

プレビューは以下のようになります。f:id:dasuko:20210301220758p:plain

ButtonStyleの指定

DefaultButtonStyle

デフォルトのボタンスタイルを適用します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .buttonStyle(DefaultButtonStyle())
    }
}

 

プレビューは以下のようになります。

gyazo.com

PainButtonStyle

特に色や枠線などの装飾のないシンプルなボタンです。

ボタンを押された時やフォーカスされた状態を示す視覚効果を適用できます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .buttonStyle(PlainButtonStyle())
    }
}

 

プレビューは以下のようになります。

gyazo.com

BorderlessButtonStyle

枠線のないボタンです。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .buttonStyle(BorderlessButtonStyle())
    }
}

 

プレビューは以下のようになります。

gyazo.com

枠線の指定

border()の引数にColorを指定するとその色の枠線をボタンに適用できます。

赤色の枠線を適用する場合は以下のようにColor.redを指定します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .border(Color.red)
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301221208p:plain

 

以下のように枠線の幅を指定することもできます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .frame(width: 300, height: 100, alignment: .center)
        .border(Color.black, width: 20)
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301221415p:plain

文字・ボタンサイズの指定

frameを指定することによりサイズを指定します。

以下のようにfontを指定することにより文字サイズを変更できます。

詳しくはこちら。

dasuko.hatenadiary.jp

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .font(.title)
        .frame(width: 300, height: 100, alignment: .center)
        .border(Color.black, width: 1)
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301221752p:plain

文字カラーの指定

foregroundColorを指定することにより、文字カラーを変更することができます。

文字色を緑色にしたい場合はColor.greenを指定します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .font(.title)
        .frame(width: 300, height: 100, alignment: .center)
        .foregroundColor(.green)
        .border(Color.black, width: 1)
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301222335p:plain

ボタンの背景色の指定

backgroundを指定することにより、背景の色を指定したり、Viewを適用したりすることができます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Button", action: {
            print("Button was pressed")
        })
        .font(.title)
        .frame(width: 300, height: 100, alignment: .center)
        .background(Color.red)
        .border(Color.black, width: 1)
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301222013p:plain

ボタンの角を丸くする

cornerRadiusを指定することにより、ボタンの角を丸くすることができます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(action: {}, label: {
            Text("Button")
        })
        .foregroundColor(Color.white)
        .padding()
        .background(Color.blue)
        .cornerRadius(100)
        .frame(width: 300, height: 100, alignment: .center)
    }
}

 

プレビューは以下のようになります。

f:id:dasuko:20210301223942p:plain

最後に

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

SwiftUIを一から極めたいという人は本を買ってみるといいかも!

SwiftUIを本気でやろうとしていない人にはお勧めしません。

 

 

参考

Apple Developer Documentation

【SwiftUI】SwiftUIでUISearchBarを使う(UIKitを使う)

はじめに

SwiftUIはとても便利ですが、UIKitにあるUIで対応していないものも多いです。

その際どのようにすれば対応できるかというのをご紹介したいと思います。

今回はUISearchBarをSwiftUIで使ってみたいと思います!!

(もちろんそのままSwiftUIのbodyの中に書くことはできないです)

 

SwiftUIの導入方法についてはこちらの記事を参考にしてください!

dasuko.hatenadiary.jp

UIViewRepresentable

UIViewRepresentableはUIKitをSwiftUIに対応させるためのラッパーです。

(カスタムビューを定義することができます)

UIKitのViewを作成・更新・破棄することができます。

UIKitにあってSwiftUIにないUIを使いたい場合はUIViewRepresentableを使おう!

 

また、UIViewRepresentableプロトコルを実装するだけでは、SwiftUIのViewに変更を通知することができません。

そこで、Coordinatorを実装することによりSwiftUIと相互に通知し合うことが可能になります。

実装

textはSwiftUIのView側で取得できる必要があるので、Binding属性を付与しています。

詳しくはこちらを確認してください。

dasuko.hatenadiary.jp

 

今回は外部からplaceholdersearchBarStyleautocapitalizationTypeを設定できるようにしてみました。

それぞれのプロパティについては以下にまとめておきます。

プロパティ 説明
text 現在のサーチバーに入力されているテキスト
placeholder テキストフィールドに何も入力していないときに表示される文字列
searchBarStyle 検索バーのスタイル
autocapitalizationType テキストの自動大文字化するかどうかの制御

 

autocapitalizationTypewordsを指定すると、自動的に各単語の最初の文字が大文字化されます。

sentencesを指定すると、各文毎に、先頭が大文字化されます。

noneを指定すると、自動的に大文字化されません。

import SwiftUI

struct SearchBar: UIViewRepresentable {
    
    @Binding var text: String
    var placeholder: String
    var searchBarStyle = UISearchBar.Style.minimal
    var autocapitalizationType = UITextAutocapitalizationType.none
    
    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text)
    }
    
    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle  = searchBarStyle
        searchBar.autocapitalizationType = autocapitalizationType
        searchBar.placeholder = placeholder
        return searchBar
    }
    
    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
    
    class Coordinator: NSObject, UISearchBarDelegate {
        @Binding var text: String
        
        init(text: Binding<String>) {
            _text = text
        }
        
        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
        }
    }
}

makeUIView

makeUIViewではUIKitのUIViewオブジェクトをインスタンス化し、初期化します。

(引数を増やしたくなくて、アプリで設定が固定の場合はここにハードコーディングでもいいかも)

updateUIView

SwiftUIが更新されたときに指定されたViewを更新します。

今回の例ではSwiftUIが更新されたときにSearchBarのテキストも同時に更新しています。

makeCoordinator

ViewやViewControllerから他のSwiftUIのUIに変更を通知するためのCoordinatorをインスタンス化します。

 

今回の例では、SearchBarでtextにBindingプロパティを設定してますが、

その値をUISearchBarに設定しても変更を受け取ることはできません。

CoordinatorのプロパティにBindingプロパティを付与することにより、

textDidChangeデリゲートでCoordinatorのtextが変更され、それにより

SearchBarのtextに設定しているSwiftUI側へ通知をすることが可能になるというわけです。

SearchBarstructなのでデリゲートを実装できない

 

使用してみる

今回実装したSearchBarを実際にSwiftUI側で使えるのか試してみます。

東京の有名な街のリストを作成してListを使って表示しています。

表示するときにはsearchTextと一致しているかどうかでフィルタリングしています。

import SwiftUI

struct ContentView: View {
    let spots = ["Asakusa", "Tokyo", "Shibuya", "Shinjuku", "Harajuku", "Ikebukuro"]
    @State private var searchText: String = ""
    
    var body: some View {
        NavigationView {
            VStack {
                SearchBar(text: $searchText, placeholder: "Search Spots")
                List {
                    ForEach(self.spots.filter {
                        self.searchText.isEmpty || $0.lowercased().contains(self.searchText.lowercased())
                    }, id: \.self) { spot in
                        Text(spot)
                    }
                }.navigationBarTitle("Spots")
            }
        }
    }
}

 

プレビューはこんな感じになります。

ちゃんと動いてますね!

gyazo.com

 

せっかくSearchBarにいくつか引数を指定できるようにしたので

searchBarStyleprominentautocapitalizationTypewordsに変更して実行してみます。

import SwiftUI

struct ContentView: View {
    let spots = ["Asakusa", "Tokyo", "Shibuya", "Shinjuku", "Harajuku", "Ikebukuro"]
    @State private var searchText: String = ""
    
    var body: some View {
        NavigationView {
            VStack {
                SearchBar(text: $searchText, placeholder: "Search Spots", searchBarStyle: UISearchBar.Style.prominent, autocapitalizationType: UITextAutocapitalizationType.words)
                List {
                    ForEach(self.spots.filter {
                        self.searchText.isEmpty || $0.lowercased().contains(self.searchText.lowercased())
                    }, id: \.self) { spot in
                        Text(spot)
                    }
                }.navigationBarTitle("Spots")
            }
        }
    }
}

 

↓プレビューはこんな感じになります。

SearchBarのスタイルが少し変化しており、かつ文字を入力したときに最初の文字が大文字になっているのがわかるかと思います。

gyazo.com

 

 

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

SwiftUIを一から極めたいという人は本を買ってみるといいかも!

SwiftUIを本気でやろうとしていない人にはお勧めしません。

 

参考

Creating a search bar for SwiftUI | by Axel Hodler | Medium

Apple Developer Documentation

 

 

【SwiftUI】Textの使い方(フォントの設定)

はじめに

この記事ではSwiftUIのTextの使い方について説明します。

Textはその名の通り(読み取り専用の)文字列を出力します。

 

ちなみにSwiftUIの導入方法についてはこちらで説明しています。

dasuko.hatenadiary.jp

 

テキストのAlignmentについてはこちらで解説しています。

dasuko.hatenadiary.jp

  VStackHStackについてはこちらを見てみてください!

dasuko.hatenadiary.jp

Textを表示してみる

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text.")
    }
}

 

↓プレビューはこんな感じになります。

f:id:dasuko:20210227134050p:plain

テキストのスタイルの指定

bold

テキストに太字のスタイルを追加します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text")
            .bold()
    }
}

  ↓プレビューはこんな感じです。

f:id:dasuko:20210227181909p:plain

italic

テキストに斜体のスタイルを追加します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text")
            .italic()
    }
}

  ↓プレビューはこんな感じです。

f:id:dasuko:20210227182129p:plain

テキストの色の設定

foregroundColorを指定することにより、テキストの色を設定することができます

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text")
            .foregroundColor(.red)
    }
}

 

↓プレビューはこんな感じです。

f:id:dasuko:20210227182510p:plain

取り消し線

strikethroughを指定することにより、テキストに取り消し線を適用することができます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text")
            .strikethrough()
    }
}

 

↓プレビューはこんな感じです。

f:id:dasuko:20210227182748p:plain

 

ちなみに取り消し線の色も指定することができます。

一つ目の引数は取り消し線を表示するかのフラグです。

falseを指定すると、取り消し線は表示されません。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text")
            .strikethrough(true, color: .red)
    }
}

 

↓プレビューはこんな感じです。

f:id:dasuko:20210227183036p:plain

アンダースコア

テキストにアンダースコア(下線)を追加します。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text")
            .underline()
    }
}

 

↓プレビューはこんな感じです。

f:id:dasuko:20210227183357p:plain

フォントの指定

テキストのフォントに標準フォントを指定したい場合は、fontで指定することができます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        Text("This is text.")
            .font(.title)
    }
}

取得できる各標準フォントの簡単な説明はこんな感じです。

フォント 説明
largeTitle 大きなタイトル
title タイトル
title2 2番目に大きなタイトル
title3 3番目に大きなタイトル
headline 見出し
subheadline 小見出し
body 本文
callout コールアウトテキスト
caption キャプション(図とかの説明文)
caption2 代替キャプション
footnote 脚注

標準フォントを比較してみる

※ここではVStackの子ビューの数に制限があるのでVStackを分けています

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            VStack {
                Text("text")
                Text("largeTitle")
                    .font(.largeTitle)
                Text("title")
                    .font(.title)
                Text("title2")
                    .font(.title2)
                Text("title3")
                    .font(.title3)
                Text("headline")
                    .font(.headline)
                Text("subheadline")
                    .font(.subheadline)
                Text("body")
                    .font(.body)
                Text("callout")
                    .font(.callout)
                Text("footnote")
                    .font(.footnote)
            }
            VStack {
                Text("caption")
                    .font(.caption)
                Text("caption2")
                    .font(.caption2)
            }
        }
    }
}

 

↓プレビューはこんな感じです。

f:id:dasuko:20210227135859p:plain

テキストのフォントの太さの指定

fontWeightでフォントのサイズを変更することができます。

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Text("text")
            Text("ultraLight").fontWeight(.ultraLight)
            Text("thin").fontWeight(.thin)
            Text("light").fontWeight(.light)
            Text("regular").fontWeight(.regular)
            Text("medium").fontWeight(.medium)
            Text("semibold").fontWeight(.semibold)
            Text("bold").fontWeight(.bold)
            Text("heavy").fontWeight(.heavy)
            Text("black").fontWeight(.black)
        }
        .font(.largeTitle)
    }
}

 

↓プレビューはこんな感じです。

f:id:dasuko:20210228212858p:plain

複数行の時の配置の指定

multilineTextAlignmentを指定することにより、テキストが複数だったときの配置を指定することができます。

以下はテキストを左に配置した例です。

import SwiftUI

struct ContentView: View {

    var body: some View {
        Text("Text\nThis is Text\nText\n")
            .multilineTextAlignment(.leading)
    }
}

 

プレビューはこんな感じになります。

f:id:dasuko:20210302234750p:plain

テキストの最大行数の指定

lineLimitを指定することにより、最大行数を指定することができます。

以下は3行のテキストに対して最大行数に2を指定しています。

import SwiftUI

struct ContentView: View {

    var body: some View {
        Text("Text\nThis is Text\nText\n")
            .lineLimit(2)
    }
}

 

プレビューこんな感じになります。

f:id:dasuko:20210302234944p:plain

最後

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

SwiftUIを一から極めたいという人は本を買ってみるといいかも!

SwiftUIを本気でやろうとしていない人にはお勧めしません。

 

 

参考

Apple Developer Documentation

【SwiftUI】ObservableObjectを使ってモデルデータを管理する(@ObservedObject、@StateObject、@EnvironmentObject)

はじめに

昨今では様々なアーキテクチャがありますが、そのほとんどがView(UI)やロジックと別にモデルデータを定義し、

これらを分離することにより、テストが容易になったり、可読性が上がったります。

SwiftUIではデータモデルクラスのオブジェクトを監視可能オブジェクト(ObservableObject)として定義することにより、

それらのプロパティが変更されたときに自動でViewを更新する仕組みがあります。

 

SwiftUIの始め方・導入の仕方はこちらの記事をご参考ください!

dasuko.hatenadiary.jp

 

@State@Bindingについてはこちらの記事で詳しく違いを説明しています!

dasuko.hatenadiary.jp

 

VStackやHStackについて説明している記事もあるので、併せて確認してみてください!

dasuko.hatenadiary.jp

監視可能なモデルデータを宣言する

モデルクラスを監視可能(Observable)なオブジェクトとして宣言するには、以下のようにObservableObjectプロトコルを適用します。

import SwiftUI

class Model: ObservableObject {
}

 

公開されたプロパティの変更を通知するためにはPublished属性を追加します。(@Published)

class Model: ObservableObject {
    @Published var text = "This is text"
}

 

もちろんObservableObjectを適用したモデルクラス内でPublished属性のない(つまりプロパティの変更を通知しない)プロパティも宣言できます

class Model: ObservableObject {
    @Published var text = "This is text"
    private var isInitialized = false
}

監視可能オブジェクトの変更を監視する

SwiftUIで監視可能オブジェクト(ObservableObject)に準拠したモデルのプロパティの変更を監視し、

変更があった場合にViewを自動的に更新するようにするには、

監視可能オブジェクト(ObservableObject)のプロパティに対して以下のいずれかの属性を付与します。

  • ObservedObject
  • StateObject
  • EnvironmentObject

ObservedObject

以下のように監視対象オブジェクトにObservedObject属性を付与します。

struct ContentView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        Text(model.text)
    }
}


class Model: ObservableObject {
    @Published var text = "This is text"
}

 

以下のように子ビューに監視対象オブジェクト(ObservableObject)のモデルを渡した場合でも

変更通知を受け取り、自動でビューが更新されます。

親ビューからObservableObjectを受け取った場合は親ビューのObservableObjectプロパティの値が変更された場合に

ビューが更新されます。

struct ContentView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        SampleView(model: model)
    }
}

struct SampleView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        Text(model.text)
    }
}

しかしSwiftUIでは定期的にビューを再構築します。

ObservedObjectでオブジェクトをインスタンス化した場合、

ビューが再構築される度にインスタンスは破棄され、再度インスタンス化されます。

つまり、値がリセットされてしまいます。(モデルデータの中身が全て初期化されてしまいます。)

 

そこで、この後に紹介するStateObjectEnvironmentObjectを使用します。

StateObject

SwiftUIはビューは再生成されることがあるため、モデルをビューの中でインスタンス化するのは危険です。

その場合はStateObject属性を付与することにより、特定のビューに対して、一度しかモデルがインスタンスかされないようになります。

struct ContentView: View {
    @StateObject var model = Model()
    
    var body: some View {
        Text(model.text)
    }
}

 

もし、親ビューでモデルをインスタンス化して、子ビューでその変更を受け取りたい場合は

親ビューのモデルにはStateObject属性を追加し、子ビューにはObservedObject属性を追加します。

struct ContentView: View {
    @StateObject var model = Model()
    
    var body: some View {
        SampleView(model: model)
    }
}

struct SampleView: View {
    @ObservedObject var model: Model
    
    var body: some View {
        Text(model.text)
    }
}

class Model: ObservableObject {
    @Published var text = "This is text"
}

EnvironmentObject

モデルを親ビューでインスタンス化して子ビューでその変更の通知を受け取りたい場合親ビューでインスタンス化するプロパティに対して

付与する属性がStateObjectでした。

しかし、ビュー単位ではなく、アプリ全体で共有したいオブジェクトもあります。

その場合にはEnvironmentObjectを使用します。

 

※この場合もインスタンス化する場合はStateObjectを使う!

ビューのインスタンス化時にView.environmentObjectにアプリ全体で共有したいオブジェクトを渡します。

受け取る側のビューの対象のプロパティにEnvironmentObject属性を付与します。

@main
struct TestApp: App {
    @StateObject var model = Model()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(model)
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var model: Model
    
    var body: some View {
        Text(model.text)
    }
}

class Model: ObservableObject {
    @Published var text = "This is text"
}

まとめ

まとめるとObservedObjectStateObjectEnvironmentObjectは以下のように使い分けされます。

属性 ユースケース
ObservedObject 主に子ビューで親ビューのオブジェクトの変更があった場合にビューを更新したいときに使用
StateObject オブジェクトをインスタンス化する場合に使用。主に親ビューで使用
EnvironmentObject アプリ共通でオブジェクトを使いたい時に使用

 

 

参考になったという方や、ここ間違っているよ!という方は

ぜひコメントしていただけると嬉しいです!!!!

 

SwiftUIを一から極めたいという人は本を買ってみるといいかも!

SwiftUIを本気でやろうとしていない人にはお勧めしません。

 

 

参考

Apple Developer Documentation

【Swift】シーケンスの高階関数まとめ

Swiftはまだまだ勉強中なので、Swiftでコレクション、シーケンスを扱う場合に重要な

高級関数をいくつか列挙し、まとめてみます!

シーケンスとは

順次要素へと反復アクセスするために用意されたプロトコルです。

シーケンスという言葉自体は、連続や順序といった意味を持ちます。

順番に並んだ一続きのデータを順番に処理することを指します。

 

シーケンスプロトコルについては

こちらに説明があります。

Apple Developer Documentation

今回紹介するシーケンスに定義されているクロージャを引数にとるメソッドは

クロージャ$0により各要素へアクセスすることができます。

 

 

それぞれの関数は結果の配列を返すので、

以下のようにいくつかの関数を連結して書くことができます。

let numbers = [0, 1, 2, nil, 3, 4]

let result = numbers
    .compactMap { $0 }
    .filter { $0 % 2 == 0}
    .map { "\($0)"}

print(result)

この例では 1. nilでない数値の配列に変換 2. 偶数の数値だけの配列に変換 3. 数値を文字列に変換

という処理をしています。

 

出力結果は以下のようになります。

["0", "2", "4"]

filter

filterろ過するという意味があります。

クロージャで指定した条件を満たす要素だけを含む配列を返します。

func filter(_ isIncluded: (Self.Element) throws -> Bool) rethrows -> [Self.Element]

クロージャはその要素を結果に含めるかどうかを判定します。

結果に含める場合はtrueを返すようにします。

 

以下の例ではInt型の配列のうち、0より小さいものだけを含む配列に変換しています。

let numbers = [-1, 2, -4, 1]

let result = numbers.filter { $0 < 0 }
print(result)

 

↓結果

[-1, -4]

map

配列に対し引数で与えたクロージャマッピングし、その結果を含む配列を返します。

要するに配列の各要素を変換します。

func map<T>(_ transform: (Self.Element) throws -> T) rethrows -> [T]

次の例ではInt型の配列をString型の配列に変換しています。

let numbers = [1, 2, 3, 4, 5]

let result = numbers.map { "\($0)" }

print(result)

 

↓結果

["Optional(1)", "Optional(2)", "Optional(3)", "Optional(4)", "Optional(5)"]

ちなみに後述するcompactMapではnilを除外しますが、

以下のようにmapnilを除外しません。

let numbers = [1, 2, 3, nil, 5, 6]

let result = numbers.map { $0 }

print(result)

 

↓結果

[Optional(1), Optional(2), Optional(3), nil, Optional(5)]

compactMap

配列に対し引数で与えた変換(クロージャ)を加えた上でnilではない要素を返します。

引数のクロージャnilを返すと、その要素は配列から除外されます。

func compactMap<ElementOfResult>(_ transform: (Self.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

次の例では、nilを含んだInt型の配列をnilを除いた配列に変換しています。

let numbers = [1, 2, 3, nil, 5]

let result = numbers.compactMap { $0 }

print(result)

↓結果

[1, 2, 3, 5]

ちなみに返り値に対してnil判定をするので、変換後の値がnilでなければ除外されません。

 

以下の例ではnilを含んだIntの配列の要素それぞれをStringに変換していますが、

nilStringに変換した場合は"nil"という文字列であり、nilではないので除外されません。

let numbers = [1, 2, 3, nil, 5]

let result = numbers.compactMap { "\($0)" }

print(result)

 

↓結果

["Optional(1)", "Optional(2)", "Optional(3)", "nil", "Optional(5)"]

flatMap

flatは平らであることや、平坦であるという意味です。

flatMapmapと同様に各シーケンスの要素に対して変換処理を実行しますが、

その結果を一つの一次元配列に連結します。

func flatMap<SegmentOfResult>(_ transform: (Self.Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

 

AppleのドキュメントにあるmapflatMapの比較の例がわかりやすいので、そのまま載せておきます。

mapでは一次元配列になるとは限りませんが、flatmapでは一次元配列となります。

let numbers = [1, 2, 3, 4]

let mapped = numbers.map { Array(repeating: $0, count: $0) }
// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]]

let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }
// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]

 

元々多次元配列の場合でも以下のように変換されます。

let numbers = [[1, 1], [2, 2], [3, 3]]

let result = numbers.flatMap { $0 }

print(result)

 

↓結果

[1, 1, 2, 2, 3, 3]

reduce

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Self.Element) throws -> Result) rethrows -> Result

指定したクロージャでシーケンスの各要素を組み合わせた結果を返します。

第一引数に初期値を指定し、第二引数に累積値と各要素を組み合わせ、新しい累積値に変換するクロージャを指定します。

以下の例では初期値を0とし、1〜4の値を順番に加算しています。

初期値は0なので最初のクロージャの引数のxとyは0と1になります。

0と1をの合計は1なので、次のxとyの値は1と2となります。

let numbers = [1, 2, 3, 4]
        
let result = numbers.reduce(0) { x, y in
    x + y
}
        
print(result)

 

全て合計すると、結果は10になります。

 

↓結果

10

もちろん乗算の場合に初期値を0にすると、結果は0になります。

let numbers = [1, 2, 3, 4]

let result = numbers.reduce(0) { x, y in
    x * y
}

print(result)

↓結果

0

lazy

シーケンスと同じ要素を返しますが、mapfilterなどの操作が遅延実行されます。

実際には、その値の取得時にシーケンスの高階関数が実行されます。

var lazy: LazySequence<Self> { get }

 

LazySequenceの定義はこんな感じです。

@frozen struct LazySequence<Base> where Base : Sequence

 

以下の例では先ほどのfilterの例で出した0以下の要素のみをもつ配列に変換していますが、

その変換を遅延実行しています。

そのため、"Test!!!!!!"というログよりも後に"map"というログが出力されています。

let numbers = [-1, 2, -4, 1]

let result = numbers
    .lazy
    .filter { $0 < 0 }
    .map({ v -> String in
        print("map")
        return "\(v)"
    })

print("Test!!!!!!")

print(Array(result))

 

↓結果

Test!!!!!!
map
map
["-1", "-4"]

次の出力結果は、上記の例からlazyを削除したものです。

その場合は当然"map"というログが最初に出力されます。

 

map
map
Test!!!!!!
["-1", "-4"]

参考

Apple Developer Documentation

シーケンス(シークエンス)とは - IT用語辞典 e-Words

イメージで理解するSwiftの高階関数(filter, map, reduce, compactMap, flatMap) - Qiita

Swift で map, compactMap, flatMap を使いこなそう - Qiita

Swiftの高階関数で遅延評価(lazy)を使い処理を効率化する - もちゅろぐ