20201122のiOSに関する記事は8件です。

【SwiftUI】Firebaseからデータ読み取り、ListViewのRowを更新する

はじめに

SwiftUIでFirebaseからデータを読み取り、これまで作成したカード内容を更新することを目的とする。

前回までの記事は以下を参考ください。

参考記事
【SwiftUi】TabViewとListの実装とViewのフォルダ管理

開発環境

OSX 10.15.7 (Catalina)
Xcode 12.2.0
CocoaPods 1.10.0

Firestoreへのドキュメントの追加

FirebaseのFirestoreデータベースは以下のように設定しました。
今回は書き込みの機能は設定していないため、直接入力しています。
今後はユーザーが投稿できるよう機能を実装します。
Haiku_-_Cloud_Firestore_-_Firebase_コンソール.png

mapImageとuserImageはFireBaseのStorageに保存してある画像のURLを入力しています。
それぞれのファイルをStorageにアップロードします。
必要なURLは"ファイルの場所"にある"アクセストークン"をクリックし、内容をコピーし、Firestoreのそれぞれのフィールドに貼り付けます。

Haiku_-_Cloud_Firestore_-_Firebase_コンソール.png

NoSQLの知識が乏しいため、データの構造化がうまくありません。この点は今後学習とともに、現在のデータ構造と変更点が出てくると思います。
現状はFirebaseの使用方法と、SwiftUIの実装方法に焦点を当てます。

参考資料
データベースの構造化
脱RDB脳!Firebase Databse導入のために考えた4つのポイント

Firestoreからのデータの読み取り

datatypestructの実装

Firebaseのデータベースと読み取りと書き出しを行うため、structを実装します。
ここで実装したStructgetDataクラスにおいて呼び出されます。
記載内容については読み取り時に、投稿日時を元に並び替えを行う場合はcreatedDateのみで大丈夫です。
今回は過去の俳人の俳句を投稿しているため、並び替えのためにnowを定義しました。

struct dataType : Identifiable {

    var id : String
    var user : String
    var haiku : String
    var place : String
    var likes : String
    var mapImage : String
    var userImage : String
    var stamp : Date
    var createdDate : String
    var now :Date
}

getDataクラスの実装

Firebaseデータベースからドキュメントを読み取るための部分です。

特にエラーが出て、実装に時間がかかった部分はTimestampのデータの取り扱いです。

Timestamp firebase Cannot convert value of type 'Timestamp' to expected argument type 'String'

Timestampのデータの変化時にエラーが出てしまい、適切にデータベースからの読み取りができませんでした。
実装にあたっては以下のサイトを参考にしました。

参考資料
Convert timestamp from Firebase to readable date
Firebase FirestoreのTimestamp型のdateへの変換

class getData: ObservableObject {

    @Published var datas = [dataType]()

    init() {

        let db = Firestore.firestore()

        db.collection("ikku").order(by: "now", descending: true).addSnapshotListener{ (snap, err) in

            if err != nil{

                print((err?.localizedDescription)!)
                return
            }

            for i in snap!.documentChanges{

                if i.type == .added{

                    let id = i.document.documentID
                    let user = i.document.get("userName") as! String
                    let haiku = i.document.get("haiku") as! String
                    let place = i.document.get("place") as! String
                    let likes = i.document.get("likes") as! String
                    let mapImage = i.document.get("mapImage") as! String
                    let userImage = i.document.get("userImage") as! String

                    let now = i.document.get("now") as! Timestamp
                    let stamp = i.document.get("createdDate") as! Timestamp

                    let formatterDate = DateFormatter()
                    formatterDate.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
                    let createdDate = formatterDate.string(from: stamp.dateValue())

                    DispatchQueue.main.async {
                        self.datas.append(dataType(id: id, user: user, haiku: haiku, place: place, likes: likes, mapImage: mapImage, userImage: userImage, stamp: stamp.dateValue(), createdDate: createdDate, now: now.dateValue() ))

                    }
                }
            }
        }
    }
}

ContentRowViewへの実装とデータベースからの情報更新

Firebaseからデータを読み取りにあたって、以下の点を変更しました。

変更前の実装については以下を参考ください。
【SwiftUi】経過時間の表示とListViewのセルデザイン

変更点
1.ContentRowViewにおけるSDWebImageSwiftUIのインポートとvarで定義した変数を空にする。

ContentRowView.swift
import SwiftUI
import Firebase

import SDWebImageSwiftUI

struct ContentRowView: View {

    var user = ""
    var userImage = ""
    var haiku = ""
    var mapImage = ""
    var place = ""
    var likes = ""
    // ○秒、○日、○年前を表示
    var createdDate = ""

    static let formatter = RelativeDateTimeFormatter()

    var body: some View {

        ContentRowView.formatter.locale = Locale(identifier: "ja_JP")

        let fmt = ISO8601DateFormatter()
        let date1 = fmt.date(from: createdDate)!
        let components = Calendar.current.dateComponents(
            [.day, .year, .month, .minute, .second],
            from: Date(),
            to: date1
        )
        let timeAgo = ContentRowView.formatter.localizedString(from: components)

        return VStack {
            VStack {
                AnimatedImage(url: URL(string: mapImage)!)
                    .resizable()
                    //.aspectRatio(contentMode: .fit)
                    .cornerRadius(12.0, antialiased: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
                HStack {
                    Spacer()
                    Text(place).font(.caption).foregroundColor(.gray)
                        .padding(.bottom, 5)
                }
            }
            Text(haiku).font(.title3).fontWeight(.bold)

            HStack {
                Image(systemName: "heart").font(.headline).foregroundColor(Color("pinkColor"))
                Text(likes).font(.headline).foregroundColor(Color("pinkColor"))
                Spacer()
            }.padding(.top, 5)


            HStack {
                AnimatedImage(url: URL(string: userImage)!)
                    .resizable()
                    .frame(width: 30, height: 30)
                    .clipShape(Circle())

                HStack {
                    Text(user).font(.headline).fontWeight(.light)
                    + Text("・").font(.headline).fontWeight(.light)
                    + Text(timeAgo).font(.headline).fontWeight(.light)
                }
                Spacer()
                HStack {

                    Image(systemName: "text.bubble").font(.title).foregroundColor(.gray)
                    Image(systemName: "heart.fill").font(.title).foregroundColor(Color("pinkColor"))
                }
            }
            Spacer()
        }.padding(.top, 8).frame(height: 391)

    }
}

2.SDWebImageSwiftUIのインポート
→URLを画像に変換するためのフレームワークを追加

AnimatedImage(url: URL(string: userImage)!)
                    .resizable()
                    .frame(width: 30, height: 30)
                    .clipShape(Circle())

3.HomeViewにおけるObservedObjectの実装
→Firebaseのデータベースからドキュメントを受け取り、ListのそれぞれのRowの情報を更新する。

home.swift
struct Home: View {

    @ObservedObject var observedData = getData()

    var body: some View {

        List(observedData.datas) { i in
            ContentRowView(user: i.user, userImage: i.userImage, haiku: i.haiku, mapImage: i.mapImage, place: i.place, likes: i.likes, createdDate: i.createdDate)
        }.environment(\.defaultMinListRowHeight, 391)
    }
}

iPhone_11_–_14_2.png

今後実装予定のもの

1.カード部分のLike機能とComment機能の実装
2.mapTabやその他のTab機能の実装
3.Post機能の実装
4.ユーザー登録機能の実装
5.俳人の俳句の登録

以上です。

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

【SwiftUI】Firebaseからデータを読み取り、ListViewのRowを更新する

はじめに

SwiftUIでFirebaseからデータを読み取り、これまで作成したカード内容を更新することを目的とする。

前回までの記事は以下を参考ください。

参考記事
【SwiftUi】TabViewとListの実装とViewのフォルダ管理

開発環境

OSX 10.15.7 (Catalina)
Xcode 12.2.0
CocoaPods 1.10.0

Firestoreへのドキュメントの追加

FirebaseのFirestoreデータベースは以下のように設定しました。
今回は書き込みの機能は設定していないため、直接入力しています。
今後はユーザーが投稿できるよう機能を実装します。
Haiku_-_Cloud_Firestore_-_Firebase_コンソール.png

mapImageとuserImageはFireBaseのStorageに保存してある画像のURLを入力しています。
それぞれのファイルをStorageにアップロードします。
必要なURLは"ファイルの場所"にある"アクセストークン"をクリックし、内容をコピーし、Firestoreのそれぞれのフィールドに貼り付けます。
Haiku_-_Storage_-_Firebase_コンソール.png

NoSQLの知識が乏しいため、データの構造化がうまくありません。この点は今後学習とともに、現在のデータ構造と変更点が出てくると思います。
現状はFirebaseの使用方法と、SwiftUIの実装方法に焦点を当てます。

参考資料
データベースの構造化
脱RDB脳!Firebase Databse導入のために考えた4つのポイント

Firestoreからのデータの読み取り

datatypestructの実装

Firebaseのデータベースと読み取りと書き出しを行うため、structを実装します。
ここで実装したStructgetDataクラスにおいて呼び出されます。
記載内容については読み取り時に、投稿日時を元に並び替えを行う場合はcreatedDateのみで大丈夫です。
今回は過去の俳人の俳句を投稿しているため、並び替えのためにnowを定義しました。

struct dataType : Identifiable {

    var id : String
    var user : String
    var haiku : String
    var place : String
    var likes : String
    var mapImage : String
    var userImage : String
    var stamp : Date
    var createdDate : String
    var now :Date
}

getDataクラスの実装

Firebaseデータベースからドキュメントを読み取るための部分です。

特にエラーが出て、実装に時間がかかった部分はTimestampのデータの取り扱いです。

Timestamp firebase Cannot convert value of type 'Timestamp' to expected argument type 'String'

Timestampのデータの変換時にエラーが出てしまい、適切にデータベースからの読み取りができませんでした。
実装にあたっては以下のサイトを参考にしました。

参考資料
Convert timestamp from Firebase to readable date
Firebase FirestoreのTimestamp型のdateへの変換

class getData: ObservableObject {

    @Published var datas = [dataType]()

    init() {

        let db = Firestore.firestore()

        db.collection("ikku").order(by: "now", descending: true).addSnapshotListener{ (snap, err) in

            if err != nil{

                print((err?.localizedDescription)!)
                return
            }

            for i in snap!.documentChanges{

                if i.type == .added{

                    let id = i.document.documentID
                    let user = i.document.get("userName") as! String
                    let haiku = i.document.get("haiku") as! String
                    let place = i.document.get("place") as! String
                    let likes = i.document.get("likes") as! String
                    let mapImage = i.document.get("mapImage") as! String
                    let userImage = i.document.get("userImage") as! String

                    let now = i.document.get("now") as! Timestamp
                    let stamp = i.document.get("createdDate") as! Timestamp

                    let formatterDate = DateFormatter()
                    formatterDate.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
                    let createdDate = formatterDate.string(from: stamp.dateValue())

                    DispatchQueue.main.async {
                        self.datas.append(dataType(id: id, user: user, haiku: haiku, place: place, likes: likes, mapImage: mapImage, userImage: userImage, stamp: stamp.dateValue(), createdDate: createdDate, now: now.dateValue() ))

                    }
                }
            }
        }
    }
}

ContentRowViewへの実装とデータベースからの情報更新

Firebaseからデータを読み取りにあたって、以下の点を変更しました。

変更前の実装については以下を参考ください。
【SwiftUi】経過時間の表示とListViewのセルデザイン

変更点
1.ContentRowViewにおけるSDWebImageSwiftUIのインポートとvarで定義した変数を空にする。

ContentRowView.swift
import SwiftUI
import Firebase

import SDWebImageSwiftUI

struct ContentRowView: View {

    var user = ""
    var userImage = ""
    var haiku = ""
    var mapImage = ""
    var place = ""
    var likes = ""
    // ○秒、○日、○年前を表示
    var createdDate = ""

    static let formatter = RelativeDateTimeFormatter()

    var body: some View {

        ContentRowView.formatter.locale = Locale(identifier: "ja_JP")

        let fmt = ISO8601DateFormatter()
        let date1 = fmt.date(from: createdDate)!
        let components = Calendar.current.dateComponents(
            [.day, .year, .month, .minute, .second],
            from: Date(),
            to: date1
        )
        let timeAgo = ContentRowView.formatter.localizedString(from: components)

        return VStack {
            VStack {
                AnimatedImage(url: URL(string: mapImage)!)
                    .resizable()
                    //.aspectRatio(contentMode: .fit)
                    .cornerRadius(12.0, antialiased: /*@START_MENU_TOKEN@*/true/*@END_MENU_TOKEN@*/)
                HStack {
                    Spacer()
                    Text(place).font(.caption).foregroundColor(.gray)
                        .padding(.bottom, 5)
                }
            }
            Text(haiku).font(.title3).fontWeight(.bold)

            HStack {
                Image(systemName: "heart").font(.headline).foregroundColor(Color("pinkColor"))
                Text(likes).font(.headline).foregroundColor(Color("pinkColor"))
                Spacer()
            }.padding(.top, 5)


            HStack {
                AnimatedImage(url: URL(string: userImage)!)
                    .resizable()
                    .frame(width: 30, height: 30)
                    .clipShape(Circle())

                HStack {
                    Text(user).font(.headline).fontWeight(.light)
                    + Text("・").font(.headline).fontWeight(.light)
                    + Text(timeAgo).font(.headline).fontWeight(.light)
                }
                Spacer()
                HStack {

                    Image(systemName: "text.bubble").font(.title).foregroundColor(.gray)
                    Image(systemName: "heart.fill").font(.title).foregroundColor(Color("pinkColor"))
                }
            }
            Spacer()
        }.padding(.top, 8).frame(height: 391)

    }
}

2.SDWebImageSwiftUIのインポート
→URLを画像に変換するためのフレームワークを追加

AnimatedImage(url: URL(string: userImage)!)
                    .resizable()
                    .frame(width: 30, height: 30)
                    .clipShape(Circle())

3.HomeViewにおけるObservedObjectの実装
→Firebaseのデータベースからドキュメントを受け取り、ListのそれぞれのRowの情報を更新する。

home.swift
struct Home: View {

    @ObservedObject var observedData = getData()

    var body: some View {

        List(observedData.datas) { i in
            ContentRowView(user: i.user, userImage: i.userImage, haiku: i.haiku, mapImage: i.mapImage, place: i.place, likes: i.likes, createdDate: i.createdDate)
        }.environment(\.defaultMinListRowHeight, 391)
    }
}

iPhone_11_–_14_2.png

今後実装予定のもの

1.カード部分のLike機能とComment機能の実装
2.mapTabやその他のTab機能の実装
3.Post機能の実装
4.ユーザー登録機能の実装
5.俳人の俳句の登録

以上です。

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

UE4でIOS/Android用のアイコン画像を爆速で入れる

UE4でIOS/Android用アイコンを爆速で入れよう

https://qiita.com/Chi__no_/items/f7d9f8e943d0da0b5f80
この記事は↑の画像作成スクリプトを実行している事が前提です。

UE4は一つ一つの画像を選択する度にプロジェクトルートから辿らないと行けないから面倒くさい。。。
そんな問題を一気に解決しちゃおうって話!

一番上の記事の内容を実行するとこんな画像ファイル達が出来ると思う。
スクリーンショット 2020-11-22 220132.png

Engineのデフォルトに設定する。

そしたらEngineのPathに行く。

Androidでは色々ファイルが分かれているから各自でやって欲しい。
参考Directory→C:\Program Files\UnrealEngine\UE_4.25\Engine\Build\Android\Java\res

IOSの場合:Engine\Build\IOS\Resources\Graphics

僕の場合は↓のPath
C:\Program Files\UnrealEngine\UE_4.25\Engine\Build\IOS\Resources\Graphics
↑のPathに行ったらとりあえずBackUpフォルダを作ってUE4のデフォルト画像をBackUpの中にコピーしておこう
スクリーンショット 2020-11-22 221051.png

そのフォルダに前記事で作った大量の画像データを全部コピーしてやる。
スクリーンショット 2020-11-22 221353.png

そうしたらUE4を再起動するだけ!
もし他の画像に変更しているなら全ての画像をデフォルトにリセット→再起動で画像が変わる。

メリットと欠点

メリット

何より早い。面倒くさい作業がほぼないから楽
画像を変えたい時もスピーディーに対応できる。

欠点

再起動したタイミングで毎回フォルダから更新するため、複数のプロジェクトを同時進行して居る時、ビルド毎に画像を差し替える必要がある。

最後に

エラー、不具合等あれば報告して下さい!
色々雑でしたがqiita初心者なので多めに見てやって下さい。

中学生の時に起業し、今高校三年生で色々なプロダクトをリリースしています!一回だけでも見ていって下さい!
https://geekline.biz ←会社のページ

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

モバイルアプリのアイコン画像用意する面倒くさくない?自動化しちゃおう!

PhotoShopでいちいち何十回も解像度を変える作業めんどくさ・・・

AndroidとiPhone合わせるとこのくらいの画像が必要になる。

b1d2c8bfdad903f18ac9aa668706984e.png

しかもiPhoneの画像は20@2xみたいな語尾を付けているからややこしい。

だからPhotoshopのスクリプトで自動化してしまおう

まず出力したいPSDのレイヤーは全てスマートオブジェクトにして下さい。←これ重要

これを忘れると出力された画像がボケるから気をつけて!
レイヤーを右クリック→スマートオブジェクトに変換
Inked4ec9e66d5b55a3bcdc164078007e697a_LI.jpg

全部出来たらこんな感じで変なマークが追加される

Inked7cc6c02a13d6c064c7f25102f02f795f_LI.jpg

スクリプトファイルダウンロード

https://drive.google.com/file/d/19dk19n3V2ciiC1BXWAHbdFAilxHzvqO_/view?usp=sharing
↑からスクリプトファイルをダウンロードしたら

PhotoShopのディレクトリを探してPhotoshop\Presets\Scripts下に配置

僕の場合は↓だった
C:\Program Files\Adobe\Adobe Photoshop 2021\Presets\Scripts

実行

念の為Photoshopでは何も開いていない状態からスタートする事をおすすめする。

メニュー→ファイル→スクリプト→MakePngsForMobileを選択

11f4fc7cd9cc70a2fbf8e2ee64994346.png

ファイル選択ダイアログが出現するので実行したいPSDを選択してOKを押す

Inkedb5816321b57c782742b12e66e15e1bdc_LI.jpg

PSDとPNGが同ディレクトリに出力されていれば成功!

47910029a03e66f30f7c9685c0912825.png

もしこのスクリプトが既存であったり、エラー、不具合等あれば報告して下さい!

色々雑でしたがqiita初投稿なので多めに見てやって下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

モバイルアプリのアイコン画像用意するの面倒くさくない?自動化しちゃおう!

PhotoShopでいちいち何十回も解像度を変える作業めんどくさ・・・

AndroidとiPhone合わせるとこのくらいの画像が必要になる。

b1d2c8bfdad903f18ac9aa668706984e.png

しかもiPhoneの画像は20@2xみたいな語尾を付けているからややこしい。

だからPhotoshopのスクリプトで自動化してしまおう

まず出力したいPSDのレイヤーは全てスマートオブジェクトにして下さい。←これ重要

これを忘れると出力された画像がボケるから気をつけて!
レイヤーを右クリック→スマートオブジェクトに変換
Inked4ec9e66d5b55a3bcdc164078007e697a_LI.jpg

全部出来たらこんな感じで変なマークが追加される

Inked7cc6c02a13d6c064c7f25102f02f795f_LI.jpg

スクリプトファイルダウンロード

https://drive.google.com/file/d/19dk19n3V2ciiC1BXWAHbdFAilxHzvqO_/view?usp=sharing
↑からスクリプトファイルをダウンロードしたら

PhotoShopのディレクトリを探してPhotoshop\Presets\Scripts下に配置

僕の場合は↓だった
C:\Program Files\Adobe\Adobe Photoshop 2021\Presets\Scripts

実行

念の為Photoshopでは何も開いていない状態からスタートする事をおすすめする。

メニュー→ファイル→スクリプト→MakePngsForMobileを選択

11f4fc7cd9cc70a2fbf8e2ee64994346.png

ファイル選択ダイアログが出現するので実行したいPSDを選択してOKを押す

Inkedb5816321b57c782742b12e66e15e1bdc_LI.jpg

PSDとPNGが同ディレクトリに出力されていれば成功!

47910029a03e66f30f7c9685c0912825.png

もしこのスクリプトが既存であったり、エラー、不具合等あれば報告して下さい!

色々雑でしたがqiita初投稿なので多めに見てやって下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Develop in Swift Data CollectionsでiOS開発を学ぶ (2): Lesson 1.3からLesson1.4

https://qiita.com/mk2/items/6091f8eb195fa3237c4e の続き

Lesson 1.3: Model-View-Controller

iOS/Mac開発といえば、MVCだと個人的には思っています。(SwiftUIの登場で考え方も変わるのかもしれませんが…)

View ControllerModel Controllerはなんとなく知っていたのですが、Helper Controllerという考え方があるのは知りませんでした。ただ、この説明を見る限り、ユーティリティ的な感じがしますね。

“Helper Controllers
Helper controllers are useful anytime you want to consolidate related data or functionality so that it can be accessed by other objects in your app. One common example of a helper controller is a NetworkController, which manages all the network requests in a given app.”

抜粋:: Apple Education “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年 Apple Books https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970

また、下記のようにソースコード、リソースをグルーピングするのが良いというのはiOS開発をがっつりやったことがないので、へーという感じでした。

“Many developers make groups for the following:
- View controllers
- Views
- Models
- Model controllers
- Other controllers
- Protocols
- Extensions
- Resources
   - Storyboards
   - Frameworks

抜粋:: Apple Education “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年 Apple Books https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970

実験

今回は、好きなアスリートを記入できるアプリのようです。最終的に作成したコードを載せておきますね。

Athlete.swift
import Foundation

struct Athlete {
    var name: String
    var age: Int
    var league: String
    var team: String

    var description: String {
        return "\(name) is \(age) years old and plays for the \(team) in the \(league)."
    }
}
AthleteTableViewController.swift
import UIKit

class AthleteTableViewController: UITableViewController {

    struct PropertyKeys {
        static let athleteCell = "AthleteCell"
    }

    var athletes: [Athlete] = []

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        tableView.reloadData()
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return athletes.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: PropertyKeys.athleteCell, for: indexPath)

        let athlete = athletes[indexPath.row]
        cell.textLabel?.text = athlete.name
        cell.detailTextLabel?.text = athlete.description

        return cell
    }

    @IBSegueAction func addAthlete(_ coder: NSCoder) -> AthleteFormViewController? {
        return AthleteFormViewController(coder: coder)
    }

    @IBSegueAction func editAthlete(_ coder: NSCoder, sender: Any?) -> AthleteFormViewController? {        let athleteToEdit: Athlete?
        if let cell = sender as? UITableViewCell,
           let indexPath = tableView.indexPath(for: cell) {
            athleteToEdit = athletes[indexPath.row]
        } else {
            athleteToEdit = nil
        }
        return AthleteFormViewController(coder: coder, athlete: athleteToEdit)
    }

    @IBAction func backToTable(_ segue: UIStoryboardSegue) {
        guard let controller = segue.source as? AthleteFormViewController,
              let athlete = controller.athlete else {
            return
        }

        if let selectedIndexPath = tableView.indexPathForSelectedRow {
            athletes[selectedIndexPath.row] = athlete
        } else {
            athletes.append(athlete)
        }
    }
}
AthleteFormViewController.swift
import UIKit

class AthleteFormViewController: UIViewController {

    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var ageTextField: UITextField!
    @IBOutlet weak var leagueTextField: UITextField!
    @IBOutlet weak var teamTextField: UITextField!

    var athlete: Athlete?

    required init?(coder: NSCoder) {
        self.athlete = nil
        super.init(coder: coder)
    }

    init?(coder: NSCoder, athlete: Athlete?) {
        self.athlete = athlete
        super.init(coder: coder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
        updateView()
    }

    func updateView() {
        nameTextField.text = athlete?.name
        if let age = athlete?.age {
            ageTextField.text = "\(age)"
        }
        leagueTextField.text = athlete?.league
        teamTextField.text = athlete?.team
    }


    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */

    @IBAction func save(_ sender: Any) {
        guard let name = nameTextField.text,
              let ageString = ageTextField.text,
              let age = Int(ageString),
              let league = leagueTextField.text,
              let team = teamTextField.text
        else {
            return
        }
        athlete = Athlete(name: name, age: age, league: league, team: team)
        performSegue(withIdentifier: "SaveAthlete", sender: self)
    }
}
わかりにくかった点

本の通りに進めていけば、多分だいたい完成させられると思うのですが、Step 6 Perform the Unwind Segue in Storyboardの節の下記の記述がよくわからずかなり四苦八苦しました。

“Finally, you need to create the unwind segue. In the storyboard, Control-drag from the athlete form scene in the Document Outline to the view controller's Exit, then choose your unwind segue. Give this segue a name by selecting it in the Document Outline and adding the identifier in the Attributes inspector.”

抜粋:: Apple Education “Develop in Swift Data Collections”。 Apple Inc. - Education、2020年 Apple Books https://books.apple.com/jp/book/develop-in-swift-data-collections/id1511183970

正解は、下のようにAthele Form View ControllerからcontrolドラッグでExitまで接続すれば良い感じでした。

Lesson1.5 Storyboard操作説明.001.jpeg

Lesson 1.4: Scroll Views

Scrolling Form

AutoLayoutの設定が難しいですね。自分は5、6回constrainsを全部消してやりなおしました。また、折角なのでiOS各端末で幅を綺麗に表示させたいと思ったのですが、そこが結構大変でした。下のように、Stack Viewの幅を親のScroll Viewのwidthと同じにすれば、各端末で同じにできるようです。

Lesson 1.4.001.png

実験

画像をズームできるようにするものでした。

ViewController.swift
import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        scrollView.delegate = self
    }

    override func viewDidAppear(_ animated: Bool) {
        updateZoomFor(size: view.bounds.size)
    }

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }

    func updateZoomFor(size: CGSize) {
        let widthScale = size.width / imageView.bounds.width
        let heightScale = size.height / imageView.bounds.height
        let scale = min(widthScale, heightScale)
        scrollView.minimumZoomScale = scale
        scrollView.zoomScale = scale
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[macOS/iOS] Autolayout覚書

はじめに

本文書では, macOS/iOSのソフトウェアのautolayout処理に関して、気づいた点/注意すべき点についてまとめます。おそらく目新しい内容はなく、よしなし事を順不同で記載します。

Autolayout処理

サイズ変更

優先順位

NSLayoutConstraint.Priorityで定義される定数の値とその意味です(macOS 10.15)。説明はマニュアルのgoogle翻訳です。

名前 即値 説明
required 1000.0 必要な制約
defaultHigh 750.0 ボタンがコンテンツの圧縮に抵抗する優先度レベル
dragThatCanResizeWindow 510.0 ウィンドウのサイズを変更する可能性のあるドラッグの適切な優先度レベル
windowSizeStayPut 500.0 ウィンドウの現在のサイズの優先度
dragThatCannotResizeWindow 490.0 たとえば、分割ビューの仕切りがドラッグされる優先度レベル
defaultLow: 250.0 ボタンがその内容を水平方向に保持する優先度レベル
fittingSizeCompression 50.0 fitingSizeの結果は、ビューのコンテンツを表示するのに十分な大きさのサイズです

Autolayout下でのViewの処理

NSTextView

  • スクロールバー付きのNSTextViewのサイズ変更については、NSTextViewそのものでなく、それを含有するNSScrollViewに対して実施する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

構造体について【Swift】

構造体とは?

  • 変数、定数、関数を1つのグループとしてまとめる仕組み。
  • 構造体の中で作られた変数・定数を プロパティ 、関数を メソッド という。
  • 構造体の中で作られた変数・定数を初期化することを イニシャライズ という。
  • 構造体を利用するにはインスタンス化が必要。
  • データ型は全て構造体である。( IntFloatDoubleStringBool

実装例

struct Human {
    // プロパティを定義
    let name: String
    let age: Int

    // initを使用することでイニシャライザ定義
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    func introduction() {
        print("私の名前は\(name)です。年齢は\(age)歳です。")
    }

}
// Human構造体をインスタンス化
let human = Human(name: "山田太郎", age: 25)
// introductionメソッドへアクセス
human.introduction() // 私の名前は山田太郎です。年齢は25歳です。

定義方法

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

プロパティとは?

インスタンスプロパティ

  • 型のインスタンスに紐付くプロパティ
  • 型のインスタンスに紐付くため、インスタンス化しなければ使うことができません。

スタティックプロパティ

  • 型自身に紐付くプロパティ
  • 型の性質を表すプロパティを定義したいとき(大規模なプロジェクト以外はあまり使う機会がない?)
  • 静的という意味なので let を推奨

インスタンスプロパティとスタティックプロパティの違い

struct Human {
    // スタティックプロパティ
    static let name = "山田太郎"
    // インスタンスプロパティ
    let age = 25 
}
// スタティックプロパティへアクセス
Human.name // OK
Human().name // NG

// インスタンスプロパティへアクセス
Human.age // NG
Human().age // OK
  • 型名().プロパティ名(型名に()をつけることでデフォルトのイニシャライザが働き、インスタンス化される)

イニシャライザとは?

  • プロパティを初期化する為のもの(他の言語ではコンストラクタとほぼ同様)
  • インスタンス化時に実行される特殊なメソッド

書式

init(引数名: 型名, ...) {
    // selfは構造体自身のインスタンスを意味する
    self.プロパティ名 = 引数を使ったりする
}

メンバーワイズイニシャライザ

  • デフォルトで用意されている特殊なイニシャライザ
  • イニシャライザは定義しない
  • 単純な初期化ならメンバーワイズイニシャライザが使えるが、イニシャライザを定義すると使えなくなる

インスタンス化の方法

イニシャライザを定義していない時

let 変数名 = 構造体名()

イニシャライザを定義した時

let 変数名 = 構造体名(引数名: 型名, ...)

メンバーワイズイニシャライザを使用する時

let 変数名 = 構造体名(プロパティ名: , ...) // プロパティを直接指定する

アクセス方法

let 変数名 = 構造体名()
変数名.プロパティ名
変数名.メソッド名

参考

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