SwiftUIでいこう! - Wheel Picker(水平方向)
以下の動画を見ながら作ってみます。
SwiftUI 2.0 Complex UI - Custom Horizontal Wheel Picker - Custom Animations - SwiftUI Tutorials
基本的なレイアウトから作っていきます。動画ではVStackでイメージ、Spaceer()を入れてテキスト2つを表示するようになっています。今回は画像は使わずテキストのみ表示させています。
そのVStackのバックグラウンドでCircle()を表示させて、位置を調整しています。
VStack(spacing:15){
}
.background(
Circle()
)
注意しないといけないのは、VStackのバックグラウンドでCircle()を書かないとPreview画面を使っていると2画面の構成になってしまいおかしくなります。
VStack(spacing:15){
Spacer()
Text("Weight")
.font(.title)
.fontWeight(.heavy)
.foregroundColor(.black)
Text("45kg")
.font(.system(size:35,weight:.heavy))
}
.frame(maxWidth:.infinity,maxHeight: .infinity)
.background(
Circle()
.fill(Color.purple)
.scaleEffect(1.5)
.offset(y: -getRect().height / 2.4)
)
これで基本的な表示部分はできました。水平方向のスクロールをつけていけば完成となります。単純なものはSwiftUIのScrollView()を使うと以下のように実装できそうですが難しいみたいです。
今回はWheel PickerはUIViewRepresentableを使って機能をつけていきます。
既存のUIKitのViewを使うときは(今回はUIScrollView())はUIViewRepresentableの以下のメソッドを最低限実装すれば使えるようになります。他機能をつけたいときはその他のメソッドをつけていきます。
func makeUIView(context: Self.Context) -> Self.UIViewType
Creates the view object and configures its initial state.
func updateUIView(Self.UIViewType, context: Self.Context)
Updates the state of the specified view with new information from SwiftUI.
ここで大事なところ、UIViewRepresentableを使ったUIKitとSwiftUIの紐付け方。今回はスクロール量を取得してその移動量で値を変化させていますがその仕組みは、
struct CustumSlider<Content:View> : UIViewRepresentable{}
構造体を作って、以下のように初期化します。
init(pickerCount:Int,offset:Binding<CGFloat>,@ViewBuilder content: @escaping ()->Content){
self.content = content()
self._offset = offset
self.pickerCount = pickerCount
}
UIKitで取得した値をSwiftUI側に反映させる部分
offset:Binding<CGFloat>
取得したものを代入する時のコードですが、
self._offset = offset
"_offset"、アンダーバーをつけたものを使うことが必要です。以下参考サイトです。
UIKitのカスタムビュー側の値をSwiftUIのView側で取得する方法
これでスクロールした分の値を反映させることができます。
UIViewRepresentableの各メソッドの説明については"UIViewRepresentableを使ってSwiftUIでちょっとリッチなWebviewを表示してみる"を参考になります。
今回のスクロールの実装の基本形は、
構造体
struct CustumSlider<Content:View> : UIViewRepresentable
を作り以下のメソッドを作ります。
func makeUIView(context: Context) -> UIScrollView {
let scrollView = UIScrollView()
return scrollView
}
updateUIViewはそのまま。実装はありません。
func updateUIView(_ uiView: UIScrollView, context: Context) {
}
以下メソッドに関しては、別クラスを作って適合させていきます。
func makeCoordinator() -> Coordinator {
return CustumSlider.Coordinator(parent: self)
}
Coordinatorに関しては、
class Coordinator:NSObject,UIScrollViewDelegate{}
を作って以下メソッドでスクロールの動きから数値をとります。
func scrollViewDidScroll(_ scrollView: UIScrollView) {
parent.offset = scrollView.contentOffset.x
}
最後にVStackでレイアウトした以下の部分に引数を入れ込んで記述します。
VStack(spacing:15){
Spacer()
Text("Weight")
Text("45kg")
// ここの部分に入れます。
}
.frame(maxWidth:.infinity,maxHeight: .infinity)
.background(
Circle()
)
カスタムCustumSlider()
CustumSlider(pickerCount: pickerCount, offset: $offset, content:{
HStack(spacing:0){
ForEach(1...pickerCount,id: \.self){index in
Rectangle()
.fill(Color.gray)
.frame(width:1,height: 30)
.frame(width:20)
ForEach(1...4,id: \.self){subIndex in
Rectangle()
.fill(Color.gray)
.frame(width:1,height: 15)
.frame(width:20)
}
}
}
.offset(x:(getRect().width - 30 / 2))
.padding(.trailing,getRect().width - 30)
})
HStack{}で横向きにスライドバーを設置、縦長四角を作ってメモリ状にします。参考動画ではそのバーの下に数字を入れたりしています。