20190523のSwiftに関する記事は6件です。

iOSアプリ開発体験会

カウントアプリを作ってみよう!!

本日は、カウントアプリを作成します??
アプリ開発の一連の流れを掴み、普段自分が使っているアプリがどうやって動いているのか体感しましょう?
↓完成図↓

では、、、、
Let's Geek!!!!!

開発環境

Swift 5.0.1
Xcode 10.1
swift&Xcode画像.png

開発準備

Xcodeを開き、
①を選択し、新しくプロジェクトを作成しましょう

②画像通りに選択して、Nextボタンを押しましょう!

③画像通りに項目を埋めて、確認が出来たらNextボタンを押しましょう!!

④アプリを作成する場所をわかりやすい「デスクトップ」に選択して、Createしましょう!!!

下の画像のようになっていたら、アプリがCreateできたことになります!!

⑤右側にあるファイルの中から、「Main.storyboard」を選択し、「View as iPhone〜」をタップし、自分の持っているiPhoneの機種と同じものを選びましょう!

これが出来たら コマンド+Rを押してシュミレーターを立ち上げて見ましょう?
→自分のパソコンにiPhone出てくるのとてもでワクワクします???

これにて、開発準備は終了です!!

開発スタート!!!

①見た目

まずは、アプリの見た目を作成していきます。
Codeを書こうとしたそこのあなた!!!ちょっと待った!!!!
まだ、Codeは書きません。
なんとXocdeを使えば、アプリの見た目をドラッグ&ドロップで作ることができるのです???
主に、Main.storyBoardを使用して作成します。
①右上の丸いボタンを押すと、「Objects」といった、いわゆるボタンや、テキストを入れれる部品を選ぶツールが開きます

②テキストを表示できる「Label」を検索し、それをスマホのような画面(ストーリーボード)にドラッグ&ドロップして配置します。

③Labelが配置できたら、ホームベースような所を押し、Labelの詳細設定をしてみましょう!!
例)
・テキスト内容を変えたい:Labelとなっている所を変更します。
・フォントを変えたい:Fontの所から選択する。
・背景色を変えたい:Backgroudから色を選択する。

Try!!:画像のように、ボタンの部品3つ配置して、色やテキストを変更してみましょう!

これで見た目は完成!!!!

②コードを書く

見た目の部分ができたので、次はいよいよCodeを書いていきます??
Codeの役目というと、例えば、ボタンを押した時にLabelのテキストが変更する、みたいなパーツにアクションを加える役目があります。
では早速、Codeを書いていきましょう?
この章では、「ViewController.swift」を選択し、Codeを書いていきます。

⑴変数を宣言する
公式:「(@IBOutlet) var (変数名): (型名) = (初期値)
例)


⑵ラベルのテキストの初期値を設定する
viewDidLoad()の{ }内に書きます。
例)
※プログラミングにおいて、「=」は「等しい」という意味ではありません!「左辺を右辺に代入する」ということを表しています!(「等しい」を表すのは「==」です。)


⑶ボタンを押した時の動作(アクション)を設定する
公式:「@IBAction func (関数名)() { (動きのコード) }
例)


コードの全体図

③関連付け

ここまでやってきたことは、見た目のパーツを作ることと、Codeを書きました。
しかし、これはそれぞれバラバラで行っているので、Codeで書いたことをパーツに関連付けてあげないと動かないのです!!
なので、最後に関連付けを行って行きたいと思います?
「Main.storyboard」を開き、ストーリーボードを選択します。そのストーリーボードに関連のあるCodeファイルを横に開くために、command+option+Enterをします!
体験会.mp4.gif

【発展】画面遷移をしてみよう

カウントが100になったら、「おめでとう」という画面が表示されるようにしてみます。
⑴おめでとう画面を作る
LabelやButtonを配置した時と同じように、右上のボタンを押して、「ViewController」を配置しましょう。

次に、この画面に「おめでとう」というLabelと「戻る」ボタンを配置して、おめでとう画面を完成させましょう。
例)
⑵Segueの準備
おめでとう画面が完成したら、画面遷移のために、遷移したい画面同士をヒモで繋ぎます(これをSegue(セグエ)と言います)。
セグエ設定.mov.gif
※画面上の黄色い丸を選択し、Controlキーを押しながら、おめでとう画面まで引っ張ります。今回は、「Present Modally」を選択しましょう。
Segueをつなぐことができたら、Identifier(IDのこと)を設定します。これは絶対忘れないように!!!

⑶おめでとう画面用のViewController(コードを書くやつ)を作成・設定
①左側で右クリックor「Control+クリック」して、「New File」を選択

②「Cocoa Touch Class」を選択して「Next」

③名前を入力(今回は「SecondViewController」)をして「Next」→「Create」


④おめでとう画面を選択し、右側のタブの左から三番目(新聞紙みたいなやつ)を選択して、今作ったSecondViewControllerを設定する

⑷コードを書く
ViewControllerを開き、以下のコードをplusボタンの続きに書きます。

Viewcontroller.swift
@IBAction func plus(){
 number +=1
 numberLabel.text = String(number)
 if number > 10 {
   performSegue(withIdentifier: "さっき書いたID", sender: nil)
 }

SecondViewControllerを開き、画像のようにアクションを書いてください。(今回、Labelはコード上で扱わないので、宣言は不要です)
スクリーンショット 2019-05-23 21.26.43.png
※関連付けを忘れずに!!
⑸動かしてみる!
ここまでできたら、シミュレーターを立ち上げて、ちゃんと動くか試してみましょう!!

みんなで勝負してみよう!!!

今後

今回の体験会で学んだアプリ開発の流れは、どのアプリでも基盤になってくるものです??
普段自分が作っているアプリがどんなパーツ構成になってるか想像してXcodeをいじってみると、とてもワクワクしてくると思います!!!
是非いろいろ触ってみて自分だけのアプリを作ってみましょう!

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

【徹底解説】UIScrollViewクラス その1

UIScrollViewクラスとは

UIScrollView(以下,SV)は,画面に触れた指などの動きを追跡することで,画面サイズよりもはみ出たコンテンツを表示させるためのUI部品です。 UITableViewやUITextViewも画面からはみ出た範囲をスクロールさせることができますが,その理由は,SVがこれらのスーパークラスであるためです。

環境

  • XCode Version 10.2.1
  • Swift 5.0

第1章 とりあえずスクロールさせる

ここではスクロールするコンテンツをstoryboardを用いて作製してみます。実践には程遠いですが何事も基本が大切なので基礎固めしていきましょう。でも本当に固まってはいけません。

SVの配置

ナビゲータエリアからMain.storyboardを選択し,Objectライブラリから「Scroll View」を選びます。それを画面(View)上にドロップします。ドロップしたSVを画面全体に広げます。もちろん必ずしも画面全体に広げる必要はありません。用途に応じてサイズを変更させてください。ここでは練習のため画面全体に広げているだけです。

ScrollView_1.png

コンテンツビューの配置

実際にスクロールさせる部品であるコンテンツビュー(contents view)を配置します。コンテンツビューにはUIViewを用いることが多いと思います。Objectライブラリから「View」を選択し,SV上にドロップします。以後分かりやすいようにViewの名前を「contentsView」に変更します。

大きさと位置を決めましょう。右上にある「Show the Size inspector」にある「width」と「height」にぞれぞれ「800」と「1200」を設定します。このcontentsViewを上下左右に青い点線が出るところに配置します。もし上手くいかなければ,「Show the Size inspector」の「X」と「Y」にそれぞれ「-193」と「-152」を入力してください(iPhoneXRを選択していること)。もちろんこれらも用途に合わせて自由に設定してください。

コンテンツビューだけではスクロールしているかどうかの判別が付かないのでUILabelを配置させましょう。Objectライブラリから「label」を選び,contentsView上にドロップさせ,上下左右に青い点線が出るところに配置させます。

ScrollView2.png

コンテンツビューのレイアウト

まだ各UI部品の位置関係についても何も制約をかけていませんでしたので制約をかけていきます。View Controllerを選択してある状態で画面右下の「Resolve Auto Layout issues」を押します。その中にある「All Views in Container」欄の「Add Missing Constraints」を押します。すると下図のように制約がかかると思います。

ScrollView_3.png

お疲れさまです。ここまできたら一旦ビルドしてみて下さい。ビルド後,画面をマウスなどで動かして見てください。おそらく動かないはずです。衝撃的ですよね。ここまで説明しておいて動かないなんて。SV上にはそれより大きなcontentsViewを配置したのでスクロールできても良さそうですが何故かできせん。Appleが悪いわけではありません。制約のかけ方が間違っているからです。下図の青色の点線で囲んだところをよく見てください。

ScrollView_4.png

例えばSVのbottomの位置をみるとcontentsViewから152下がった位置で制約がかかっています。上下左右ともSVとcontentsViewの位置はガチガチに制約がかかっている状態です。この状態ではスクロールはできません。

それではスクロールができるように制約をかけ直しましょう。まず上の青い点線で囲んだ「Constraints」を消します。次に,contentsViewを選択し,画面右下にある「Add New Constraints」を押し,4つとも「0」を入力します。そして最後に「Add 4 Constraints」を押し,ビルドしてください。今度はスクロールできるはずです。

ScrollView_1.gif

スクロールできた理由が大切です。SVの上下左右の位置とcontentsViewの上下左右の位置を一致させたためです。これによって,スクロールできる範囲は,左方向に関してはcontentsViewの左端がSVの左端に一致するまでとなりました。その他の位置も同様です。

第2章 コードでやってみる

SVの配置のみstoryboard上で行い,上記で行ったコンテンツビューの配置などはコードでやってみましょう。

Outlet接続

SV以外のUIは全て削除し,SVをscrollViewという名前でOutlet接続させます。

ScrollView_6.png

コンテンツビューの作製

コンテンツビューとその中身を作っていきましょう。以下がコード全体になります。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var scrollView: UIScrollView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        configureSV()
    }

    func createLabel(contentsView: UIView) -> UILabel {

        // labelを作る
        let label = UILabel()

        // labelの座標をcontentsViewの中心にする
        let labelX = contentsView.center.x
        let labelY = contentsView.center.y
        label.frame = CGRect(x: labelX, y: labelY, width: 95, height: 50)

        label.text = "Label"
        return label
    }

    func createContentsView() -> UIView {

        // contentsViewを作る
        let contentsView = UIView()
        contentsView.frame = CGRect(x: 0, y: 0, width: 800, height: 1200)

        // contentsViewにlabelを配置させる
        let label = createLabel(contentsView: contentsView)
        contentsView.addSubview(label)

        return contentsView
    }

    func configureSV() {

        // scrollViewにcontentsViewを配置させる
        let subView = createContentsView()
        scrollView.addSubview(subView)

        // scrollViewにcontentsViewのサイズを教える
        scrollView.contentSize = subView.frame.size
    }

}

それでは細かく見ていきましょう。

func createLabel(contentsView: UIView) -> UILabel

ラベルを生成するメソッドです。

let label = UILabel()
// labelの座標をcontentsViewの中心にする
let labelX = contentsView.center.x
let labelY = contentsView.center.y
label.frame = CGRect(x: labelX, y: labelY, width: 95, height: 50)
label.text = "Label"
return label

引数であるcontentsViewの中心にlabelが配置するように設定しています。returnでそのlabelを返します。

func createContentsView() -> UIView

コンテンツビューを生成するメソッドです。

let contentsView = UIView()
contentsView.frame = CGRect(x: 0, y: 0, width: 800, height: 1200)
// contentsViewにlabelを配置させる
let label = createLabel(contentsView: contentsView)
contentsView.addSubview(label)
return contentsView

func configureSV()

SVの設定を行うメソッドです。

// scrollViewにcontentsViewを配置させる
let subView = createContentsView()
scrollView.addSubview(subView)
// scrollViewにcontentsViewのサイズを教える
scrollView.contentSize = subView.frame.size

スクロールさせるためにはSVにコンテンツビューの大きさを教える必要があります。SVのcontentSizeプロパティーにその大きさを設定することでSVにコンテンツビューの大きさを教えることができます。

これでビルドすると第1章と同様にスクロールさせることができると思います。ただ現状では,初期の「label」の位置が右下付近によっています。上下左右の中央にずらしたいですが,初期位置に関しては別の章で説明します。

第3章 横スクロールさせる

第1,2章だけではあまり面白みがありません。SVを使用する時は,横か縦方向にスクロールさせることが多いと思います。この章では横スクロールに焦点を当て,もうすこし実践的にしましょう。

スクロールで色が変わる画面を作る

次のようにスクロールさせることでページごとに色が変わるのアプリケーションを作りましょう。

Scroll_color.001.png

スクロールビューのみstoryboardを使用して,それ以外はコードで実装していきます。任意ですが,スクロールビューの制約はSafeAreaに対して行います。全体のコードです。

ViewController.swift
import UIKit

class ViewController: UIViewController {

    let numberOfPages = 5
    let colors: [UIColor] = [.yellow, .gray, .red, .blue, .brown]

    @IBOutlet weak var scrollView: UIScrollView!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        configureSV()
    }

    func createPages(page: Int) -> UIView {
        let pageView = UIView()
        let pageSize = scrollView.frame.size
        let positionX = pageSize.width * CGFloat(page)
        let position = CGPoint(x: positionX, y: 0)
        pageView.frame = CGRect(origin: position, size: pageSize)
        pageView.backgroundColor = colors[page]

        return pageView
    }

    func createContentsView() -> UIView {
        let contentsView = UIView()
        let contentsWidth = scrollView.frame.width * CGFloat(numberOfPages)
        let contentsHeight = scrollView.frame.height
        contentsView.frame = CGRect(x: 0, y: 0, width: contentsWidth, height: contentsHeight)

        for i in 0 ..< numberOfPages {
            let pageView = createPages(page: i)
            contentsView.addSubview(pageView)
        }

        return contentsView
    }

    func configureSV() {
        let contentsView = createContentsView()
        scrollView.addSubview(contentsView)
        scrollView.contentSize = contentsView.frame.size
    }

}

細かく見ていきましょう。

インスタンスプロパティー

 numberOfPages:ページ数
 colors:色の設定

func createPages(page: Int) -> UIView

ページを生成するメソッドです。

let pageView = UIView()
let pageSize = scrollView.frame.size
let positionX = pageSize.width * CGFloat(page)
let position = CGPoint(x: positionX, y: 0)
pageView.frame = CGRect(origin: position, size: pageSize)
pageView.backgroundColor = colors[page]
return pageView

ページの幅はscrollViewと同じにしています。引数pageは現在のページの番号です。ページがスクロールした分だけページの位置がずれ,その値をpositionXに格納します。pageの背景色はcolorsに格納されている色を順に取り出して
backgroundColorで設定します。

func configureSV()

前章と同じでSVの設定を行います。

さて,これでビルドすると以下のようにスクロールができると思います。

Scroll_color.gif

スクロールビューの設定

ここではスクロールビューにどんな設定ができるかを見てみましょう。すべてBool型で設定します。

インスタンスプロパティー名 trueの時の内容 デフォルト値
isScrollEnabled スクロール可能 true
isDirectionalLockEnabled 最初にスクロールさせた方向(水平か垂直)のみスクロール可能 false
isPagingEnabled 1ページずつスクロールする false
scrollsToTop ステータスバーをタップすると一番上までスクロールする true
bounces スクロールがコンテンツの一番端に到達すると跳ね返る(バウンドする) true
alwaysBounceVertical コンテンツがスクロールビューよりも小さい場合でも常に垂直方向のバウンドを許可する。ただしbounces=trueであることも必要 false
alwaysBounceHorizontal コンテンツがスクロールビューよりも小さい場合でも常に水平方向のバウンドを許可する。ただしbounces=trueであることも必要 false

例えば,ページ単位でスクロールするには以下のように設定します。

let scrollView = UIScrollView()
scrollView.isPagingEnabled = true

記事が長くなってきたのでここで一旦区切ります。第4章以降は別記事で説明します。お疲れ様でした。

続編

【徹底解説】UIScrollViewクラス その2(執筆中)

参考文献

詳細! Swift iPhoneアプリ開発入門ノート 著者:大重 美幸 出版社: ソーテック社


ご意見等あれば連絡ください。

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

Animations in iOS: Tab Bar concepts

We see tab bars every day.

They guide users inside the app, allowing them to quickly switch between different tabs. Apple provides a nice into in their Human Interface Guidelines:

Tab bars are translucent, may have a background tint, maintain the same height in all screen orientations, and are hidden when a keyboard is displayed. A tab bar may contain any number of tabs, but the number of visible tabs varies based on the device size and orientation. If some tabs can’t be displayed due to limited horizontal space, the final visible tab becomes a More tab, which reveals the additional tabs in a list on a separate screen
But who said mobile navigation should be boring?

Let’s explore interesting animations inside the tab bars. I focused first on the ones made for iOS platform. And mentioned several useful guides to implement animated tab bars in Swift ?

Some of the animations are from real apps. While others — are just nice design concepts, made for inspiration.

Would you use some of them for your apps?

1_yTtJ9dHsouqXC4y3B3oO5g.gif

WeChat Tab Bar Redesign by Adrian Reznicek for PLATFORM

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Animated tab bar concept by Cadabra Studio

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Camera app tab bar [iPhone X edition] concept by Oleg Frolov for Magic Unicorn

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Yoga App Menu Concept by Dannniel

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Create a New Document Tab Bar concept by Hoang Nguyen

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Fluid Tab Bar Interaction concept by Oleg Frolov

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Animated Tab Bar Icons — Interface concept by Andrew McKay

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Animated Tab Bar Icons — Interface concept by Andrew McKay

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Tab bar icons by Dimest

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Tab bar active animation by Aaron Iker, based on Valentin Tsymbaluk concept

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Animated Tab Bar Icons — Interface concept and Swift implementation by Ramotion

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Tab bar interaction concept by Boyang Zhang

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Animated Tab Bar concept by Mauricio Bucardo

1_yTtJ9dHsouqXC4y3B3oO5g.gif

Tab bar interaction concept by Kaiseir

If you want to practice with designing or developing tab bars, these guides will help you out:

  • Human Interface Guidelines on Tab Bars, where you’ll learn best practices in a quick 4 min read.

  • Guide on “Starting an iOS Tab Bar App with UITabBarViewController”.

  • 29 implemented examples .

  • Swift UI module library for adding animation to iOS tab bar.

Hope you enjoyed this small inspirational peace. You can also check 30 beautiful examples of animations for iOS and Everything you need to know about Loading Animations

Happy designing and developing!

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

Realmで配列を扱う!?

はい!タイトル詐欺です!Realmで配列を扱うことはできません!

じゃあどうするのかというと、「List」という型を使ってデータに紐付けをしていくというのが今回の内容です。

まずRealmとは

簡単にいうとデータベースです

Realm公式:https://realm.io/jp/
RealmSwiftリファレンス:https://realm.io/docs/swift/latest/api/index.html

SwiftでRealmを扱うには「RealmSwift」というライブラリを使用します。

RealmSwiftで扱える「型」

詳しくはリファレンスを見ていただきたいんですが、主に扱える型としては
String
Int
Bool
Date
List
などになります

今回はこの「List」に焦点を当てていきます

Listについて

公式リファレンスの説明をgoogleさんに翻訳してもらいました

「List 1対多の関係を定義するために使用されるRealmのコンテナタイプです。」

よくわかんないですね

Swift的にわかりやすく説明すると

["task": "夏休みの宿題", "ticket": ["英語", "算数", "社会"]]

ちょっと違うかもしれないですけどこんな感じですかね?

言葉で説明すると
「'夏休みの宿題' というタスクが '英語','算数','社会' のチケットを持っている」
という感じでしょうか?

余計わかりにくなった?とりあえずこれをRealmで書いてみます

モデルの定義

それでは早速Realmの定義を書いていきましょう!

インストールなどについてはわかりやすい記事がたくさんあるのでご自身で調べてみてください
https://qiita.com/leegun/items/70414223b2339b6052ee

まずはクラスを作っていきます
今回は先ほど例に出したタスクとチケットのモデルを定義していきます

import RealmSiwf

class Task: Object {  
    @objc dynamic var taskTitle: String = ""
}

class Ticket: Object {
    @objc dynamic var ticketTitle: String = ""
}

Realmでモデルを定義する際には
@objc dynamic
と記載します

タスクとチケットのモデルが定義できました!

ただこれだとタスクとチケットが紐付いていません・・・

Listを使って1対多の関係を提示してあげましょう

import RealmSiwf

class Task: Object {  
    @objc dynamic var taskTitle: String = ""
    //Listの定義
    let tickets = List<Ticket>()
}

class Ticket: Object {
    @objc dynamic var ticketTitle: String = ""
}

Listを定義する際は
List<Element>
としてあげる必要があります

これで定義は完了しました!

では実際にデータを追加したり参照したりしてみましょう

データモデルの追加

Realmのインスタンスを取得し、そこにデータを挿入していきます

do {
    //インスタンスの取得
    let realm = try Realm()
    let dictionary: [String: Any] =
        ["taskTitle": "夏休みの宿題",
         "tickets": [["ticketTitle": "算数"],
                     ["ticketTitle": "英語"],
                     ["ticketTitle": "社会"]]
        ]

    let task = Task(value: dictionary) //Taskモデルのインスタンスの作成

    //書き込み処理
    try! realm.write { 
        realm.add(task)
        print(task)
    }
}
catch {
    print(error)
}

プリント結果

Task {
    taskTitle = 夏休みの宿題;
    tickets = RLMArray&lt;Cat&gt; &lt;0x1c0306150&gt; (
        [0] Ticket {
            ticketTitle = 算数;
        },
        [1] Ticket {
            ticketTitle = 英語;
        },
        [2] Ticket {
            ticketTitle = 社会;
        }
    );
}

Taskのインスタンスにタスク名とチケットを挿入して、実際のデータに書き込んでいます。
データに書き込んだり、またそのデータを削除したりなどの処理は
realm.write{}
の中にコーディングする必要があるので気をつけてください!

データモデルの取得(Results)

追加したデータモデルを取得してみましょう

Realmデータベースに保存されている特定のモデルクラスのオブジェクトを全て取得するにはRealmクラスのobjects(_:)を使用します。

do {
    //インスタンスの取得
    let realm = try Realm()

    //オブジェクトの取得
    let results = realm.objects(Task.self)
    print(results)
}
catch {
    print(error)
}

プリント結果

Results<Task> <0x104423670> (
    [0] Task {
        taskTitle = 夏休みの宿題;
          tickets = RLMArray<Ticket> <0x1c41193e0> (
          [0] Ticket {
            ticketTitle = 算数;
          },
          [1] Ticket {
            ticketTitle = 英語;
          },
          [2] Ticket {
            ticketTitle = 社会;
          }
        );
    }

List側の削除と追加

まずはチケットを追加する処理を書いてみましょう

通常Realmでデータを追加するときは"add"を使用するのですが、List型の場合"append"で追加します。

do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)

    let ticket = Ticket(value: ["ticketTitle": "国語"]) //Ticketモデルのインスタンスの作成

    print("追加前",results)
    // 追加処理
    try! realm.write {
        for task in results {
            task.tickets.append(ticket)
        }
    }
    print("追加後",results)
}
catch {
    print(error)
}

プリント結果

追加前 Results<Task> <0x7ff8c650f470> (
    [0] Task {
        taskTitle = 夏休みの宿題;
        tickets = List<Ticket> <0x600003d84360> (
            [0] Ticket {
                ticketTitle = 算数;
            },
            [1] Ticket {
                ticketTitle = 英語;
            },
            [2] Ticket {
                ticketTitle = 社会;
            }
        );
    }
)
追加後 Results<Task> <0x7ff8c650f470> (
    [0] Task {
        taskTitle = 夏休みの宿題;
        tickets = List<Ticket> <0x600003d84a20> (
            [0] Ticket {
                ticketTitle = 算数;
            },
            [1] Ticket {
                ticketTitle = 英語;
            },
            [2] Ticket {
                ticketTitle = 社会;
            },
            [3] Ticket {
                ticketTitle = 国語;
            }
        );
    }
)

では次に削除してみましょう!
Listには「removeLast()」や「removeFirst()」などのメソッドがあり最初のデータや最後のデータなどを指定して削除できます
今回は「remove(at:Int)」を使用して特定の値を削除してみたいと思います

do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)

    print("削除前",results)
    // 削除処理
    try! realm.write {
        for task in results {
            for (index, ticket) in task.tickets.enumerated() {
                if ticket.ticketTitle == "国語" {
                    task.tickets.remove(at: index)
                }
            }
        }
    }
    print("削除後",results)
}
catch {
    print(error)
}

プリント結果

削除前 Results<Task> <0x7fc26af135e0> (
    [0] Task {
        taskTitle = 夏休みの宿題;
        tickets = List<Ticket> <0x600003d5b9f0> (
            [0] Ticket {
                ticketTitle = 算数;
            },
            [1] Ticket {
                ticketTitle = 英語;
            },
            [2] Ticket {
                ticketTitle = 社会;
            },
            [3] Ticket {
                ticketTitle = 国語;
            }
        );
    }
)
削除後 Results<Task> <0x7fc26af135e0> (
    [0] Task {
        taskTitle = 夏休みの宿題;
        tickets = List<Ticket> <0x600003d51950> (
            [0] Ticket {
                ticketTitle = 算数;
            },
            [1] Ticket {
                ticketTitle = 英語;
            },
            [2] Ticket {
                ticketTitle = 社会;
            }
        );
    }
)

ちょっとネストがきつくなってしまいましたがご愛嬌ということで

おまけ!

タイトルで配列云々と言っていので、上記で作成したデータを配列に戻してみます

do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)

    var taskArray = ["taskTitle":"", "tickets":[]] as [String : Any]
    var ticketArray: Array<String> = []

    for task in results {
        taskArray["taskTitle"] = task["taskTitle"]
        for ticket in task["tickets"] as! List<Ticket> {
            ticketArray.append(ticket.ticketTitle)
        }
    }
    taskArray["tickets"] = ticketArray
    print(taskArray)
}
catch {
    print(error)
}

プリント結果

["taskTitle": 夏休みの宿題],"tickets": ["算数", "英語", "社会"]]

こんな感じでしょうか?

最後に

Realmは直感的にデータをいじれるので使っていて楽しいです
自分もまだまだ理解できていないことがたくさんあるので習得に励んでいこうと思います!

皆さんも良いRealmライフを!!

追記

@t_naganoさんからのご指摘!
Realmからデータを取り出すとき、「map」を使用してあげるとより簡潔に取り出せます!

do {
    //インスタンスの取得
    let realm = try Realm()
    let results = realm.objects(Task.self)

    var taskArray = ["taskTitle":"", "tickets":[]] as [String : Any]
    var ticketArray = [String]()

    for task in results {
        taskArray["taskTitle"] = task["taskTitle"]
        // for ticket in task["tickets"] as! List<Ticket> {
        //     ticketArray.append(ticket.ticketTitle)
        // }
        ticketArray = task.tickets.map { $0.ticketTitle } // mapを使って取り出す
    }
    taskArray["tickets"] = ticketArray
    print(taskArray)
}
catch {
    print(error)
}

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

ReleaseビルドでMKMapViewDelegateのメソッドが呼び出されなくなった話

はじめに

これは、ReleaseビルドしたiOSアプリで MKMapViewDelegate の下記のようなメソッドが呼び出されないことがある現象に遭遇し、腑には落ちないけどなんとか解消したというメモです。

func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?

状況

環境

  • Xcode 10.2
  • Swift 5
  • iOS 11 or 12

(Xcode 10.1 + Swift 4.2 のころに、この問題は発生していませんでした)

実装

アプリ内に MKMapView を表示するコントローラクラスが複数存在し、それらを抽象化した親クラス BaseMapViewControllerMKMapViewDelegate を宣言していました。(宣言のみで実装は無し)

class BaseMapViewController: UIViewController, MKMapViewDelegate {
    @IBOutlet weak var mapView: MKMapView!

    // MKMapViewDelegateの実装はなし
}

子クラス FirstMapViewController, SecondMapViewController, ThirdMapViewController... が実際にそれぞれの delegate メソッドを実装していましたが、Releaseビルドにおいて FirstMapViewControllerrendererFor は呼び出されるのに、SecondMapViewControllerrendererFor は呼び出されないという不思議現象。(Debugビルドでは想定通り SecondMapViewController のメソッドも呼び出されていました)

class FirstMapViewController: BaseMapViewController {
    func showOverlay() {
        mapView.addOverlay(overlay, .aboveRoads)
    }

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        // 呼び出される?
        return FirstRenderer()
    }
}

class SecondMapViewController: BaseMapViewController {
    func showOverlay() {
        mapView.addOverlay(overlay, .aboveRoads)
    }

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        // 呼び出されない❌☠️
        return SecondRenderer()
    }
}

class ThirdMapViewController: BaseMapViewController {
    func showAnnotation() {
        mapView.addAnnotation(annotation)
    }

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
        // 呼び出される?
        return ThirdView()
    }
}

class FourthMapViewController: BaseMapViewController {
    func showAnnotation() {
        mapView.addAnnotation(annotation)
    }

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
        // 呼び出されない❌☠️
        return FourthView()
    }
}

*もちろん実際の実装はもっと複雑で、このコードだとおそらくちゃんと呼び出されると思うのですが、雰囲気が伝わればということでご了承ください。(なにしろ、実装を変更していないのに、突如呼び出されなくなってしまったので)

回避策

BaseMapViewControllerrendererForviewFor の空実装を定義し、それぞれの子クラスでそれらの delegate メソッドを override すると、リリースビルドでも正しく delegate メソッドが呼ばれるようになりました。

class BaseMapViewController: UIViewController, MKMapViewDelegate {
    @IBOutlet weak var mapView: MKMapView!

    func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        // 空実装
        return MKOverlayRenderer()
    }

    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
        // 空実装
        return nil
    }
}

class FirstMapViewController: BaseMapViewController {
    override func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        // 呼び出される?
        return FirstRenderer()
    }
}

class SecondMapViewController: BaseMapViewController {
    override func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
        // 呼び出された!!⭕️?
        return SecondRenderer()
    }
}

class ThirdMapViewController: BaseMapViewController {
    override func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
        // 呼び出される?
        return ThirdView()
    }
}

class FourthMapViewController: BaseMapViewController {
    override func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?
        // 呼び出された!!⭕️?
        return FourthView()
    }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftプログラミングにおけるGuard構文

運命のピラミッド

If ステートメントが過度にネストされたコードのこと。

運命のピラミッド
func singBirthdaySong() {
    if isBirthday {
        if guests > 0 {
            if haveCakes {
                print("Happy birthday to you!")
            } else {
                print("We need the cakes!!!")
            }
        } else {
            print("It's just my family.")
        }
    } else {
        print("It's not anyone's birthday...")
    }
}

コードを目で追っても、本当に実行したいコードになかなか辿り着かない...。

Guard構文のキホン

If 構文とは制御フローが逆な感じなので、注意。

guard構文
guard condition else {
    // condition == false の場合に実行したいコード
    // 関数内なら return で脱出できる
}
// condition == true の場合に実行したいコード

運命のピラミッドをリファクタリング

いわゆる 早期リターン な記述になるので、コードが読みやすくなる。

guard構文
func singBirthdaySong() {
    guard isBirthday else {
        print("It's not anyone's birthday...")
        return
    }

    guard guests > 0 else {
        print("It's just my family.")
        return
    }

    guard haveCakes else {
        print("We need the cakes!!!")
        return
    }

    print("Happy birthday to you!")
}

Guard 構文によって、本当に実行したいコードが関数定義内の最下部に移動した。

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