iOS

SwiftUIの微妙な所とその対処法

SwiftUIの登場で画面の開発効率はあがりましたが、一方で今まで普通に出来ていたことがすぐ出来ず戸惑う場面も増えました。

この記事ではSwiftUIの微妙な所とその対処法を紹介していきます。
新しい微妙な所を発見したら随時追加していきます。

 

Listの罫線が勝手に短くなる問題

Listはとても簡単に表を作ってくれて便利なのですが左寄りになるため、ちょっと不格好なレイアウトになってしまいます。

そこで次のようなコードを書くとセンタリングにできますが、罫線が微妙なことになります。

let fruits = [
    "りんご", "バナナ", "みかん", "イチゴ"
]

var body: some View {
    List {
        ForEach(fruits, id: \.self) { fruit in
            Text(fruit)
                .frame(maxWidth: .infinity, alignment: .center) // コレ追加
        }
    }
}

なぜか罫線が短く….

罫線は最初に出てくるTextの位置に反応しているようなので次のようにコードを書き直すといい感じになります。

List {
    ForEach(fruits, id: \.self) { fruit in
        HStack {
            Text("") // ダミー
            
            Text(fruit)
                .frame(maxWidth: .infinity, alignment: .center)
        }
    }
}

 

同様に、Imageを最初に置いたListでも罫線が短くなりますが、

次のようにコードを書けば全体に罫線が引かれます。

List {
    ForEach(fruits, id: \.self) { fruit in
        HStack {
            Text("") // ダミー
            
            Image(systemName: "star.leadinghalf.filled")
            
            Text(fruit)
                .frame(maxWidth: .infinity, alignment: .center)
        }
    }
}

 

これを応用すれば間延びになりがちなToggle(Switch)も

センタリングだけすると罫線が変なことになって微妙だけど…

こう書けば

List {
    ForEach(fruits, id: \.self) { fruit in
        HStack {
            Text("")
            
            Toggle(isOn: $dummy) {
                Text(fruit)
                    .frame(maxWidth: .infinity, alignment:
.center)
            }
        }
    }
}

こうなります!やったね。

 

Listのcellのタップ範囲が狭い問題

一番シンプルなListはこんなコードになるけど、これだと文字の部分しかタップが反応しません。

struct ContentView: View {
    let fruits = [
        "りんご", "バナナ", "みかん", "イチゴ"
    ]
    
    var body: some View {
        List {
            ForEach(fruits, id: \.self) { fruit in
                Text(fruit)
                    .onTapGesture {
                        print(fruit)
                    }
            }
        }
    }
}

行のどこを押しても反応するようにするには、コードをこう変えるとOKです。

List {
    ForEach(fruits, id: \.self) { fruit in
        HStack {
            Text(fruit)
            
            Spacer() //これ足す
        }
        .contentShape(Rectangle()) // これも足す
        .onTapGesture {
            print(fruit)
        }
    }
}

これでもOK

List {
    ForEach(fruits, id: \.self) { fruit in
        Text(fruit)
            .frame(maxWidth: .infinity, alignment: .leading)
            .contentShape(Rectangle())
            .onTapGesture {
                print(fruit)
            }
    }
}

 

Listのタップ処理に飲み込まれてcellに置いたボタンが反応しない問題

Listの中にボタンも配置するのは良くあると思いますが、Listのタップ判定が優先されてボタンが反応しません。

List {
    ForEach(fruits, id: \.self) { fruit in
        HStack {
            Text(fruit)
            
            Spacer()
            
            Button(action: {
                print("ボタンタップだよ")
            }) {
                Image(systemName: "bitcoinsign.circle")
            }
        }
        .contentShape(Rectangle())
        .onTapGesture {
            print("行タップだよ")
        }
    }
}

こんな時は、コードをこう変えるとボタンがちゃんと反応するようになります。
(.buttonStyle(PlainButtonStyle())をButtonに足す)

List {
    ForEach(fruits, id: \.self) { fruit in
        HStack {
            Text(fruit)
            
            Spacer()
            
            Button(action: {
                print("ボタンタップだよ")
            }) {
                Image(systemName: "bitcoinsign.circle")
            }
            .buttonStyle(PlainButtonStyle()) // コレ足す
        }
        .contentShape(Rectangle())
        .onTapGesture {
            print("行タップだよ")
        }
    }
}

 

Buttonのタップ範囲が文字だけ問題

Buttonのframeを大きくセットしてみためを大きくしてもButtonのタップ範囲は文字の部分だけになります。

Button(action: {
    print("押した!")
}) {
    Text("押してください!")
}
.frame(maxWidth: .infinity, maxHeight: 44)
.background(Color.orange)

ボタン全体をタップ範囲にするには文字列の部分にframeをセットするとOKです。

Button(action: {
    print("押した!")
}) {
    Text("押してください!")
        .frame(maxWidth: .infinity, maxHeight: .infinity) //
コレ足す
}
.frame(maxWidth: .infinity, maxHeight: 44)
.background(Color.orange)

 

Buttonを押しても文字しか半透明にならない問題

おしゃれなButtonにしようと思って、Buttonに枠線や角丸をセットしても、押した時には文字の部分しか半透明にならず、押したかどうかが視覚的に分かりづらくなります。

Button(action: {
    print("押した!")
}) {
    Text("押してください!")
        .frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(width: UIScreen.main.bounds.width-40, height: 60)
.foregroundColor(Color(UIColor.darkGray))
.background(Color.white)
.cornerRadius(30)
.overlay(
    RoundedRectangle(cornerRadius: 30)
        .stroke(Color(UIColor.darkGray), lineWidth: 1.0)
)

 

こういう場合は、ボタンの装飾をButton側ではなくText側に適用することでButton全体を半透明にできるようになります。

Button(action: {
    print("押した!")
}) {
    Text("押してください!")
        .frame(width: UIScreen.main.bounds.width-40, height:
60)
        .foregroundColor(Color(UIColor.darkGray))
        .background(Color.white)
        .cornerRadius(30)
        .overlay(
            RoundedRectangle(cornerRadius: 30)
                .stroke(Color(UIColor.darkGray), lineWidth:
1.0)
        )
}