Hey Everyone,
I've been remaking an app using SwiftUI, and I am running into a weird animation bug/hitch using .sheet(isPresented:)
.
I have a GIF illustrating the issue, but can't seem to attach it to this forum post. Let me know if there's anyway to share this.
Regardless, my issue:
To describe the issue: I'm using two Detents for the sheet, [.medium, .large]
. I have a TextField
that's displayed in the sheet, and when opening it, the keyboard moves my content upward (expected, working fine).
My issue comes when I programmatically resign the TextField (using .focused($isFocused)
. The content on the sheet jumps up, beyond the upper bound of the sheet. My hypothesis is that the sheet's content is immediately redrawn, using the medium detent frame, but before the animation has finished going from large
to medium
.
It's possible this is not a SwiftUI bug, but something wrong with my implementation. I'll provide the relevant code below. Any help is greatly appreciated!
Onboarding.swift
(presents the sheet)
@ViewBuilder
var content: some View {
VStack {
headline
.foregroundStyle(.white.opacity(0.95))
subHeadline
.foregroundStyle(.white)
Spacer()
messages
.foregroundStyle(.white)
Spacer()
callToAction
}
.ignoresSafeArea(.keyboard)
.sheet(isPresented: $showJoin) {
join
}
}
var join: some View {
Join()
.ignoresSafeArea()
.presentationCornerRadius(40)
.presentationDragIndicator(.hidden)
.presentationBackground {
Rectangle()
.fill(.ultraThinMaterial)
.padding(.bottom, -1000)
}
.presentationDetents([.medium, .large])
}
Join.swift
(holds the sheet's content, and displays the heading)
struct Join: View {
@State private var didSignUp = false
var body: some View {
VStack {
heading
Divider()
contentView
}
.animation(.easeInOut, value: didSignUp)
.transition(.opacity)
.interactiveDismissDisabled(didSignUp)
}
var heading: some View {
VStack(spacing: 8) {
Text(didSignUp ? "Verify" : "Start here")
.frame(maxWidth : .infinity, alignment: .leading)
.font(.largeTitle.bold())
.foregroundColor(.primary)
.blendMode(.overlay)
Text(didSignUp ? "Enter code" : "Create an account")
.frame(maxWidth : .infinity, alignment: .leading)
.font(.callout)
.foregroundColor(.primary)
.blendMode(.overlay)
}
.padding(.top, 20)
.padding(.horizontal)
}
var contentView: some View {
Group {
if didSignUp {
Verification()
.transition(.move(edge: .trailing).combined(with: .opacity))
} else {
SignUp(didSignUp: $didSignUp)
.transition(.move(edge: .leading).combined(with: .opacity))
}
}
.padding(.horizontal)
}
}
SignUp.swift
(the sheet content)
struct SignUp: View {
@Binding var didSignUp: Bool
@State private var phoneNumber: String = ""
@State private var sendingTextMessage = false
@FocusState private var isFocused: Bool
private let notice =
"""
By creating an account, you agree to our **[Terms of Service](https://cordia.app/tos)** and **[Privacy Policy](https://cordia.app/privacy)**
"""
var body: some View {
VStack {
VStack {
phoneNumberLabel
phoneNumberInput
}
.padding()
if sendingTextMessage {
LoadingIndicator(isVisible: $sendingTextMessage)
.padding()
} else {
continueButton
.padding()
}
Spacer()
termsAndConditions
.padding(.bottom)
}
}
var phoneNumberLabel: some View {
Text("Enter your phone number")
.font(.title3)
.foregroundColor(.primary)
.blendMode(.overlay)
.frame(maxWidth: .infinity, alignment: .leading)
}
var phoneNumberInput: some View {
iPhoneNumberField("(***) 867-5309", text: $phoneNumber)
.maximumDigits(10)
.formatted()
.clearsOnEditingBegan(true)
.clearButtonMode(.always)
.font(.system(size: 25, weight: .semibold))
.padding()
.glass(cornerRadius: 10)
.focused($isFocused)
}
var termsAndConditions: some View {
Text(addPolicyLinks() ?? AttributedString(notice))
.multilineTextAlignment(.center)
.fixedSize(horizontal: false, vertical: true)
.font(.body)
.blendMode(.overlay)
.padding()
}
var continueButton: some View {
Button(action: {
guard !sendingTextMessage else { return }
sendingTextMessage = true
isFocused = false
UIImpactFeedbackGenerator(style: .rigid).impactOccurred()
Auth.signUp(with: phoneNumber) { user, error in
didSignUp = true
}
}) {
Text("Join Cordia")
.font(.system(size: 25, weight: .semibold))
.foregroundColor(.primary.opacity(0.8))
.frame(width: 200, height: 60)
.tintedGlass(
cornerRadius: 20,
strokeColor: .cordiaGoldDark,
strokeSize: 1.0,
tint: .cordiaGold
)
}
}
private func addPolicyLinks() -> AttributedString? {
var string = try? AttributedString(markdown: notice)
if let range = string?.range(of: "Terms of Service") {
string?[range].foregroundColor = .cordiaGold
}
if let range = string?.range(of: "Privacy Policy") {
string?[range].foregroundColor = .cordiaGold
}
return string
}
}