はじめに
前回は選択されたCardをタップしたら、リスト一覧を出せるようにしました。
しかし、選択されたcoursesは固定のままで、今回はそれぞれのCardをそれぞれのリストを出せるようにします。
目次
disabledとDispatchQueue
・まずはCourse.swift
Courseを新たに作ったModel
Groupに移動します。(MVVM)
・現状では、show
がtrueかflaseかを条件としてCardの状態を管理しています。
全てのCardは自分が持つリストを認識させるように、まずはseletedItem
を作ります。
デフォルトでnil(Optional)をつめて、Cardがまだ選択されていない状態を意味します。
@State var show = false
@Namespace var namespace
@State var selectedItem: Course? = nil
・タップジェスチャーはまだループの外にあるので、Cardの中に移動します。
また、どのCardをタップしたのかをわかってもらうために、クロージャのitem
をselectedItem
につめていきます。
ForEach(courses) { item in
CourseItem(course: item)
.matchedGeometryEffect(
id: item.id, in: namespace, isSource: !show
)
.frame(width: 335, height: 250)
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
selectedItem = item
}
}
}
・Card中のListは↑上からselectedItem
をもらって、自分専属のリストゲットできるようになりました。
・ただ、今はCardを開くことしかできなくて、閉じる場合はCardと同じく、ListもonTapGesture()
を使います。また、Cardを閉じることによって、selectedItem
もいらなくなるので、nilをつめていきます。
if selectedItem != nil {
ScrollView {
CourseItem(course: selectedItem!)
.matchedGeometryEffect(
id: selectedItem!.id, in: namespace
)
.frame(height: 300)
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
selectedItem = nil
}
}
.......
・これで9割ができましたが、タップするスピードが早すぎですと、CardとCardが重なってしまいます。 解決するロジックもシンプルですが、Card①のアニメーションが終わるまで他のCardをdisableにします。
@State var isDisabled = false
ForEach(courses) { item in
CourseItem(course: item)
.matchedGeometryEffect(
id: item.id, in: namespace, isSource: !show
)
.frame(width: 335, height: 250)
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
selectedItem = item
isDisabled = true
}
}
.disabled(isDisabled)
}
・同様に、Cardの中でdisableにしたため、Listでdisableを外します。
DispatchQueue
を使ってアニメーションを自由に遅らせることができるので、0.5秒を遅らせば綺麗にできました。
if selectedItem != nil {
ScrollView {
CourseItem(course: selectedItem!)
.matchedGeometryEffect(
id: selectedItem!.id, in: namespace
)
.frame(height: 300)
.onTapGesture {
withAnimation(.spring()) {
show.toggle()
selectedItem = nil
DispatchQueue.main.asyncAfter(
deadline: .now() + 0.5
) {
isDisabled = false
}
}
}
.......
まとめ
ソースコードGithub