スポンサードリンク

アプリ編#9 画面レイアウトの調整【SwiftUI】

スマートカーテン

やぎ星人です。どうもこんにちは。
先日の記事にも少し書きましたが、ここ数日原因不明の腰痛にかなり悩まされました。
対策をいろいろ講じましたが、結果は「背筋を鍛える」ことで解決しました。(?)
運動不足で背筋が衰え、増加した自重を支えるのが大変だったのでしょうかね。
背筋さんには申し訳ないことをしたなと、反省の意を込めながら背筋を補強中です。

やぎ星人

目指せ背筋マッチョ

ポニ丸

きもちわるい

さて、今回はUIパーツの配置の調整を行いたいと思います。
前回の記事での課題を一つずつ解決していきます。

今回の目標

ContentView.swiftファイルを修正し、カーテン制御機能(開作動・閉作動・停止)画面をUIデザインに少しでも近づけたいと思います。

<UIデザインイメージ>

<悲しい現状>

修正ポイントは以下としました。

  1. UIパーツ表示位置の調整
  2. UIパーツサイズの調整
  3. ステータスバー(画面上のバー)の色を白→黒に変更

まずは表示位置・サイズの調整から

目標1,2はすんなり解決しました。

スペーサーなるものを追加してみる

表示位置についてはSpacerビューを追加することで調整しました。
試しに配置したImageビューの間にSpacerビューをいくつか追加してみました。
さらにbackgroundモディファイアを使用し可視化してみました。

こんな感じで追加しました。
Spacer().background(Color.blue)

Spacerビュー:
 文字通り空白を表示するためのビューです。
backgroundモディファイア:
 引数に指定したビューが背景に表示されます。

実行結果がこちら
あれれ?

なんとなく空白は挿入できたのですが、backgroundモディファイアで指定した青色は反映できませんでした。Spacerビューには着色ができないのでしょうか..
仕方ないのでお描きツールでイメージを表現します。下の①〜⑦の7箇所にSpacerビューを挿入しました。

こちらが実際のソースコード(抜粋)です。

struct ControlView: View {
    var body: some View {
        ZStack{
            Color("background")
            VStack(){
                Spacer().background(Color.blue)
                // 窓とカーテンのイメージ画像
                ZStack(){
                    Image("blue_sky")
                    Image("role_curtain05")
                }
                Spacer().background(Color.blue)
                HStack(){
                    Spacer().background(Color.blue)
                    // カーテン開けるボタン
                    Image("openBtn")
                    Spacer().background(Color.blue)
                    // カーテン閉めるボタン
                    Image("closeBtn")
                    Spacer().background(Color.blue)
                }
                Spacer().background(Color.blue)
                // 作動を止めるボタン
                Image("stopBtn")
                Spacer().background(Color.blue)
            }
        }
    }
}

ちにみにSpacerビューのモディファイア「background」は反映されないことがわかったので、後で削除しました。
この状態でSpacerビューやImageビューのサイズを良い感じに変更できれば、各ビューの表示位置やサイズを狙い通りに調整できると考えました。

各ビューのサイズを変更してみた

ビューのサイズの変更については、frameモディファイアを使用することで実現できました。

frameモディファイアのイニシャライザはこちらです。
ビューのフレームの横幅、高さ、アライメント(オブジェクトの配置を設定 ex.右揃え/中央揃えとか)を設定することができます。各パラメータは省略可能で、省略した場合はデフォルト値(nil, nil, 中央揃え)が指定されるようです。

.frame(width: CGFloat?, height: CGFloat?, alignment: Alignment)

試しにImageビューに適用してみましたが、サイズが変わりませんでした。
書籍やネットで調べたところ、Imageビューのサイズを変更するには「resizable」モディファイアを使用し、画像をリサイズする必要があるとのこと。また画像のリサイズ時によく使用されるモディファイアとして「scaledToFit」や「scaledToFill」というものがあるそうです。

resizableモディファイア:
 画像をフレームサイズに合わせてリサイズする
scaledToFit:
 縦横比を保ち、画像をフレームサイズにピッタリ収める
scaledToFill:
 縦横比を保ち、画像をフレームサイズいっぱいに表示
 フレームの縦横比によっては画像が見切れる可能性あり

今回は画像が見切れないように「scaledToFit」モディファイアを使用します。
試しに、Imageサイズを横75*50として表示してみました。

ZStack(){
    Image("blue_sky")
        .resizable()
        .scaledToFit()
    Image("role_curtain05")
        .resizable()
        .scaledToFit()
}
.frame(width: 75, height: 50)

こちらが実行結果です。

ということで、ビューのサイズ変更方法についてはframeモディファイアを使用し、Imageビューの場合はさらにresizableモディファイア、scaledToFitモディファイアを使用すれば良さそうです。

ここで一つ疑問が..

やぎ星人

スマホの画面サイズは多様。
frameモディファイアでサイズを決め打ちで指定する方法で大丈夫だろうか..
画面サイズに応じてビューパーツのサイズを調整する必要があるのでは?

スマホの画面サイズに応じてUIパーツのサイズを計算したほうがよさそうですね。
このままじゃiPhone8の画面サイズにしか対応しないアプリが完成しちゃいそうですもんね。
こちらも書籍やインターネットでお勉強することに。

答えはGeometryReader

前述した課題を解決するためにいろいろと調査しました。結果として行き着いた答えが「GeometryReader構造体を使用する」ことでした。

説明が難しいのですが、GeometryReaderを使用すると親となるビューのサイズや座標を取得できるようです。
気になっていろいろと調べていたらFunctionBuilderとか知らない概念が登場して、ワクワクしました。Swiftについては駆け出しで分からないことが多いのですが、少しずつ言語仕様を学んでいきたいと思います。

実際の使用方法としては以下のように記述します。

GeometryReader { XXXX in
内部処理
}

“内部処理”の部分はビューを返すような処理を記述します。Image()とか。
XXXXの部分は任意の変数名です。こちらは構造体「GeometryProxy」型の変数として定義されます。
この構造体変数のメンバにアクセスすることで、親となるビューの情報を取得できるようです。
各ファイルの先頭に記述している「import SwiftUI」処理を右クリックして”Jump to Definition”を選択するとSwiftUIの定義ファイルを開くことができました。「GeometryProxy」でファイル内検索すると構造体の定義が見つかりました!

size: CGSize ・・・親ビューのサイズ(水平・垂直方向)を参照できる
safeAreaInsets: EdgeInsets ・・・親ビューのセーフエリア(UIが干渉しないように設けられてる安全領域)情報を参照できる
frame -> CGRect ・・・親ビューのframe情報を参照できる

試しに簡単なコードを書いて動作確認してみました。
親ビュー画面上から3分の1の位置より、親ビュー画面の2分の1サイズのColorビューを配置するというもの。

import SwiftUI

struct ContentView: View {
    var body: some View {
        GeometryReader{ proxy in
            VStack(){
                Spacer()
                    .frame(height: proxy.size.height / 3)
                Color.blue
                    .frame(height: proxy.size.height / 2)
                Spacer()
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

実行結果はこんな感じです。指定した比率でColorビューを配置することができました。

ということで、GeometryReaderで画面との相対サイズを計算し、各パーツ配置やサイズを調整してみました。

こちらがコード修正結果です。

//
//  ControlView.swift
//  SmartCurtain
//
//  Created by yagi seijin on 2020/08/24.
//  Copyright © 2020 Loose Life Hack. All rights reserved.
//

import SwiftUI

struct ControlView: View {
    var body: some View {
        ZStack{
            Color("background")
            GeometryReader{ geometry in
                VStack(){
                    Spacer()
                        .frame(height: geometry.size.height * 0.1)
                    // 窓とカーテンのイメージ画像
                    ZStack(){
                        Image("blue_sky")
                            .resizable()
                            .scaledToFit()
                        Image("role_curtain05")
                            .resizable()
                            .scaledToFit()
                    }
                    .frame(width: geometry.size.width * 0.9, height: geometry.size.height * 0.4)
                    
                    Spacer()
                        .frame(height: geometry.size.height * 0.1)
                    
                    HStack(){
                        Spacer()
                        // カーテン開けるボタン
                        Image("openBtn")
                            .resizable()
                            .scaledToFit()
                            .frame(width: geometry.size.width * 0.4)

                        Spacer()
                        // カーテン閉めるボタン
                        Image("closeBtn")
                            .resizable()
                            .scaledToFit()
                            .frame(width: geometry.size.width * 0.4)
                        Spacer()
                    }
                    Spacer()
                        .frame(height: geometry.size.height * 0.05)
                    // 作動を止めるボタン
                    Image("stopBtn")
                        .resizable()
                        .scaledToFit()
                        .frame(width: geometry.size.width * 0.4)
                    Spacer()
                }
            }
        }
    }
}

struct ControlView_Previews: PreviewProvider {
    static var previews: some View {
        ControlView()
    }
}

そしてこちらが実行結果

無事にUIパーツの位置、サイズを調整できました。
ステータスバーの色変更もやりたかったのですが、長くなりそうなので今回はここまでとします。

それではまた次回の記事でお会いしましょう!

コメント