I’ve the next TabView
TabView(choice: $currentTab) {
ForEach(0...(quizViewModel.quizzes.rely), id: .self) {
if $0 < quizViewModel.quizzes.rely {
quizSide(index: $0)
.tag($0)
} else {
resultSide()
.tag($0)
}
}
}
.tabViewStyle(.web page(indexDisplayMode: .by no means))
.indexViewStyle(.web page(backgroundDisplayMode: .all the time))
After I swipe from quizSide
to a different quizSide
, the animation is clean.
Clean animation
Nevertheless, once I swipe from quizSize
to the final web page resultSide
, or from final web page resultSide
to quizSide
, the animation is jumpy.
Jumpy animation
I believed the animation needs to be clean, as I’ve already utilized id
and tag
contained in the TabView
.
Do you will have any concept why is is so? How I can keep away from such? This is the whole code snippet.
struct QuizView: View {
@Atmosphere(.colorScheme) personal var colorScheme
@Atmosphere(.dismiss) var dismiss
@ObservedObject var quizViewModel: QuizViewModel
@State personal var currentTab = 0
@State personal var isSheetPresented = false
personal let successBackgroundColor = Shade(hex: "388E3C")
var physique: some View {
NavigationView {
VStack {
if quizViewModel.quizzes.isEmpty {
VStack(spacing: 0) {
Textual content("quizzes_loading...")
.foregroundColor(.secondary)
.font(.title)
LottieView(animation: .named("chat"))
.looping()
.body(width: 200, peak: 56)
}.padding(.horizontal, 16)
} else {
Spacer()
TabView(choice: $currentTab) {
ForEach(0...(quizViewModel.quizzes.rely), id: .self) {
if $0 < quizViewModel.quizzes.rely {
quizSide(index: $0)
.tag($0)
} else {
resultSide()
.tag($0)
}
}
}
.tabViewStyle(.web page(indexDisplayMode: .by no means))
.indexViewStyle(.web page(backgroundDisplayMode: .all the time))
// Navigation Buttons
HStack {
Button(motion: {
withAnimation {
currentTab = max(0, currentTab - 1)
}
}) {
Picture(systemName: "chevron.left.circle.fill")
.font(.system(dimension: 44))
.foregroundStyle(.white, .blue)
}
.opacity(currentTab > 0 ? 1 : 0.3)
Spacer()
if currentTab < quizViewModel.quizzes.rely {
Textual content(verbatim: "(currentTab+1) / (quizViewModel.quizzes.rely)")
.multilineTextAlignment(.middle)
.lineLimit(1)
.minimumScaleFactor(0.5)
.foregroundColor(.secondary)
.font(.physique)
Spacer()
}
Button(motion: {
withAnimation {
currentTab = min(quizViewModel.quizzes.rely, currentTab + 1)
}
}) {
Picture(systemName: "chevron.proper.circle.fill")
.font(.system(dimension: 44))
.foregroundStyle(.white, .blue)
}
.opacity(currentTab < quizViewModel.quizzes.rely ? 1 : 0.3)
}
.padding(.horizontal, 16)
.padding(.backside, 16)
}
}
.navigationBarTitle("quiz", displayMode: .inline)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(motion: { dismiss() }) {
Picture(systemName: "xmark")
.imageScale(.medium) // This matches UIKit's "medium" image scale
.foregroundColor(.main) // This matches UIKit's "Label" colour
}
}
}
}
.navigationViewStyle(StackNavigationViewStyle()) // Power single-column navigation (iPad)
}
}
extension QuizView {
personal func resultSide() -> some View {
VStack {
Textual content(verbatim: "🥳")
.foregroundColor(.main)
.font(.largeTitle)
}
.body(maxWidth: .infinity, maxHeight: .infinity) // Ensures VStack fills the entire display
.background(.pink) // Background now covers the whole display
}
personal func quizSide(index: Int) -> some View {
VStack {
let choice = quizViewModel.picks[index]
let userMadeSelection = choice >= 0
let quiz = quizViewModel.quizzes[index]
if userMadeSelection {
if let clarification = quiz.selections[selection].clarification, !clarification.isTrimmedEmpty {
let right = choice == quiz.reply
let backgroundColor: Shade = (right ? successBackgroundColor : Shade(UIColor.systemRed))
.opacity(colorScheme == .darkish ? 0.4 : 0.2)
let content material =
Textual content(clarification)
.foregroundColor(.main)
.font(.physique)
.padding(.vertical, 8)
.padding(.horizontal, 16)
.body(maxWidth: .infinity, alignment: .main)
.quizCard(backgroundColor: backgroundColor, cornerRadius: 8)
let textual content = Textual content("tap_for_explanation")
.foregroundColor(.blue) // Blue colour like a hyperlink
.underline() // Provides an underline
.font(.physique)
.onTapGesture {
isSheetPresented = true // Present the underside sheet
}
ViewThatFits(in: .vertical) {
content material
textual content
}
.padding(.prime, 0)
.padding(.backside, 16)
.sheet(isPresented: $isSheetPresented) {
NavigationView {
content material
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(motion: {
isSheetPresented = false
}) {
Picture(systemName: "xmark")
.imageScale(.medium) // Matches UIKit's "medium" image scale
.foregroundColor(.main) // Matches UIKit's "Label" colour
}
}
}
}
.presentationDetents([.medium])
}
}
}
VStack {
HStack(alignment: .prime) {
Textual content(verbatim: "(index + 1). ")
.foregroundColor(.main)
.font(.title3)
.fontWeight(.daring)
Textual content(verbatim: "(quiz.query)")
.foregroundColor(.main)
.minimumScaleFactor(0.5)
.font(.title3)
.fontWeight(.medium)
.lineLimit(3)
.multilineTextAlignment(.main)
}
.body(maxWidth: .infinity, alignment: .main)
.padding(.backside, 8)
ForEach(Array(quiz.selections.enumerated()), id: .offset) { buttonIndex, selection in
Button(motion: {
quizViewModel.picks[index] = buttonIndex
}) {
let right = buttonIndex == quiz.reply
let chosen = choice == buttonIndex
let foregroundColor: Shade = right ? Shade("successTextColor") : Shade(UIColor.systemRed)
HStack(alignment: .middle) {
Textual content(selection.textual content)
.foregroundColor(.main)
.minimumScaleFactor(0.5)
.font(.title3)
.lineLimit(2)
.multilineTextAlignment(.main)
Spacer()
let systemName = right ? "checkmark.circle.fill" : "xmark.circle.fill"
let opacity = right ? (userMadeSelection ? 1.0 : 0.0) : (chosen ? 1.0 : 0.0)
Picture(systemName: systemName)
.font(.system(dimension: 22))
.foregroundStyle(.white, foregroundColor)
.opacity(opacity)
}
.padding(.vertical, 8)
.padding(.horizontal, 16)
.body(maxWidth: .infinity, minHeight: 56, alignment: .main)
.background(Shade.grey.opacity(0.2))
.cornerRadius(8)
.overlay(
chosen ?
RoundedRectangle(cornerRadius: 8)
.stroke(foregroundColor, lineWidth: 2)
: nil
)
}
}
}
.padding(.vertical, 24)
.padding(.horizontal, 16)
.quizCard()
.padding(.backside, 16)
// https://stackoverflow.com/questions/56507497/views-compressed-by-other-views-in-swiftui-vstack-and-list
.fixedSize(horizontal: false, vertical: true)
}
.body(maxHeight: .infinity, alignment: .backside)
}
}