はじめに
iOSデバイスでPCコントローラーを作る《こちらの記事》で使っている、トグル式の角丸ボタンの紹介です。
『swiftui 角丸ボタン』でググると、角丸ボタンの作り方がいくつもヒットしますが、今回作成するボタンは『トグル式』です。
『トグル式』とは
下記のGIFアニメの通り、ボタンを押すと凹んだ状態を維持し、もう一度押すと元に戻るボタンのことです。(「プッシュプル式」が正しい?)
「凹んだ状態」を制御
角丸ボタンに「凹んだ状態」であることを意味するプロパティisSelectedを追加して、ボタンの背景色と文字色を入れ替えるだけです。これに加えて、このプロパティを親Viewから変更することで、ボタンを押す操作以外でも状態を変えることが必要でした。
角丸にして背景を塗る
基本形は次のコードとなります。
struct ContentView: View {
let foreColor = Color.accentColor
let backgroundColor = Color.white
@State var isSelected: Bool = false
var body: some View {
VStack {
// ↓↓↓
Button(action: {
print("Button Pushed!")
}) {
Text("Button")
.bold()
.padding()
.frame(width: 100, height: 50)
.foregroundColor(isSelected ? backgroundColor: foreColor)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.black, lineWidth: 3)
)
.background(isSelected ? foreColor : backgroundColor)
}
// ↑↑↑
Button("toggle") {
isSelected.toggle()
}
}
}
}
Button
ボタンの下のtoggle
ボタンを押すと、角丸ボタンの外観を変更します。
角丸ボタンのアクション内でisSelected.toggle()
を実行すれば、『ボタンを押すと凹んだ状態を維持し、もう一度押すと元に戻るボタン』の完成。
次に、このボタンを汎用化(関数化)します。
RoundedRectangleSelectableButton
⭐️印で示したisSelected
がtrue
の場合に凹んだ状態を示し、false
の場合に、元に戻します。
struct RoundedRectangleSelectableButton: View {
let text: String
let textColor: Color
let backgroundColor: Color
let width: CGFloat
let height: CGFloat
let cornerRadius: CGFloat
let borderColor: Color
let borderWidth: CGFloat
let action: (_ selected: Bool)->Void
private var autoSelect: Bool? = nil
@Binding private var isSelected: Bool //⭐️
init(_ text: String, textColor: Color = Color(uiColor: .tintColor), backgroundColor: Color = Color(uiColor: .systemBackground), width: CGFloat, height: CGFloat, cornerRadius: CGFloat = 8, borderColor: Color = Color(uiColor: .label), borderWidth: CGFloat = 2, isSelect: Binding<Bool>, action: @escaping (_ selected: Bool)->Void) {
self.text = text
self.textColor = textColor
self.backgroundColor = backgroundColor
self.width = width - 4
self.height = height
self.cornerRadius = cornerRadius
self.borderColor = borderColor
self.borderWidth = borderWidth
self.action = action
_isSelected = isSelect
}
var body: some View {
Button(action: {
if let autoSelect = self.autoSelect, autoSelect {
isSelected.toggle()
}
action(isSelected)
},label: {
Text(text)
.foregroundColor(isSelected ? Color.white : textColor)
.frame(width: width, height: height)
.background(isSelected ? Color(uiColor: .tintColor) : backgroundColor)
.cornerRadius(cornerRadius)
.overlay(
RoundedRectangle(cornerRadius: cornerRadius)
.stroke(borderColor, lineWidth: borderWidth)
)
})
}
func autoSelect(_ autoSelect: Bool) -> Self {
var view = self
view.autoSelect = autoSelect
return view
}
}
引数の説明
引数 | 意味 | 型 | 省略値 |
---|---|---|---|
text | ボタンのラベル | String | -(省略不可) |
textColor | ラベルの色 | Color | UIColor.tintColor |
backgroundColor | 背景色 | Color | UIColor.systemBackground |
width | 幅 | CGFloat | -(省略不可) |
height | 高さ | CGFloat | -(省略不可) |
cornerRadius | 角丸のサイズ | CGFloat | 8 |
borderColor | 枠線の色 | Color | UIColor.label |
borderWidth | 枠線の太さ | CGFloat | 2 |
isSelect | 凹み状態の変数 | Binding<Bool> | -(省略不可) |
action | ボタンが押された時に実行するアクション | (_ selected: Bool)->Void | -(省略不可) |
色の省略値にシステム色を使用することで、ダークモード時もいい感じにしてくれます。
オプション(View Modifier)
名称 | 引数の型 | 意味 | オプション 省略値 |
---|---|---|---|
autoSelect | Bool | 自動でトグル切り替えするかどうか。 自動にする:true、しない:false |
false |
使い方
次のコードで、3つの使い方を説明します。
struct ContentView: View {
let foreColor = Color.accentColor
let backgroundColor = Color.white
@State var isSelect1: Bool = false
@State var isSelect2: Bool = false
@State var isSelect3: Bool = false
@State var isSelect4: Bool = false
var body: some View {
VStack {
VStack {
Text("パターン1")
RoundedRectangleSelectableButton("Normal", width: 100, height: 40, isSelect: $isSelect1, action: { _ in
//action: todo
})
Button("Toggle") {
isSelect1.toggle()
}
}
.padding(.bottom, 50)
VStack {
Text("パターン2")
RoundedRectangleSelectableButton("Toggle", width: 100, height: 40, isSelect: $isSelect2, action: { _ in
//action: todo
})
.autoSelect(true)
}
.padding(.bottom, 50)
VStack {
Text("パターン3")
HStack {
RoundedRectangleSelectableButton("Option A", width: 100, height: 40, isSelect: $isSelect3, action: { _ in
isSelect3.toggle()
if isSelect3 { isSelect4 = false }
//action: todo
})
Text("or")
RoundedRectangleSelectableButton("Option B", width: 100, height: 40, isSelect: $isSelect4, action: { _ in
isSelect4.toggle()
if isSelect4 { isSelect3 = false }
//action: todo
})
}
}
}
}
}
パターン1
角丸の普通のボタン。引数isSelectの設定で親ビューから外見を変更可能です。
Toggle
をタップすると外観が変化します。
パターン2
角丸の普通のトグル式ボタン。.autoSelect(true)
を指定することで、トグル切り替えを自動化しています。
パターン3
「オプションを選択する」とかで使用するボタンです。「Option A」か「Option B」か両方無しの組み合わせだけ許可します。.autoSelect
は使わずに、引数isSelectで指定した変数を制御することで実現しています(グループ化の機能があれば、もっと楽できますね)。
おわりに
グループ化の機能は、今後追加しようと思います。
よかったら使ってみてください。
以上です。