animationswiftui

SwiftUI Animation Issue (After Text assign)


enter image description here

I use the title and description computedProperties in the PopupType enum in PopupView. While the animation is offset, why do the Texts come later?

MainView:

VStack(spacing: 30) {
                            Spacer()
                            Button {
                                authManager.popupType = .warning(title: "Are you sure ?", desc: "Do you wanna sign out ?")
                            } label: {
                                Text("Sign Out")
                            }
                        }
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                        .padding(.bottom, 16)
                        .overlay {
                            PopupView(
                                firstButtonTitle: Constant.okButtonTitle,
                                actionButtonTitle: Constant.goBackBtnTitle,
                                status: $authManager.popupType)
                        }

PopupType:

enum PopupType: Equatable {
    case none
    case warning(title: String, desc: String)
    case success(String)
    case error(String)
    
    var icon: String {
        switch self {
        case .none:
            return "circle"
        case .warning:
            return "exclamationmark.circle"
        case .success:
            return "checkmark.circle"
        case .error:
            return "xmark.circle"
        }
    }
    
    var title: String {
        switch self {
        case .none:
            return ""
        case .warning(let title, _):
            return title
        case .success:
            return "Başarılı"
        case .error:
            return "Hata"
        }
    }
    
    var description: String {
        switch self {
        case .none:
            return ""
        case .warning(_, let desc):
            return desc
        case .success(let desc):
            return desc
        case .error(let desc):
            return desc
        }
    }
    
    var iconColor: Color {
        switch self {
        case .none:
            return .clear
        case .warning:
            return Color(uiColor: .orange)
        case .success:
            return Color(uiColor: .green)
        case .error:
            return Color(uiColor: .red)
        }
    }
}

PopupView:

struct PopupView: View {
    enum ActionType {
        case dismiss
        case custom
    }
    
    var showActionButton: Bool = true
    var firstButtonTitle: String = ""
    var actionButtonTitle: String = ""
    var actionType: ActionType = .custom
    @Binding var status: PopupType
    var onAction: (() -> ())? = { }
    @Environment(\.dismiss) var dismiss
    var body: some View {
        VStack {
            Spacer()
            VStack(spacing: 16) {
                Image(systemName: status.icon)
                    .resizable()
                    .frame(width: 50, height: 50)
                    .fontWeight(.thin)
                    .colorMultiply(status.iconColor)
                
                Text(status.title)
                    .bold()
                
                Text(status.description)
                    .font(.system(size: 16, weight: .light))
                    .multilineTextAlignment(.center)
                
                HStack {
                    Button(action: { status = .none }, label: {
                        Text(firstButtonTitle)
                            .bold()
                            .frame(maxWidth: .infinity)
                            .frame(height: 50)
                            .foregroundStyle(Color(uiColor: .systemBlue))
                            .background(.ultraThinMaterial)
                            .cornerRadius(8.0)
                    })
                    if showActionButton {
                        Button(action: {
                            switch actionType {
                            case .dismiss:
                                dismiss()
                            case .custom:
                                onAction?()
                            }
                            status = .none
                        }, label: {
                            Text(actionButtonTitle)
                                .bold()
                                .frame(maxWidth: .infinity)
                                .frame(height: 50)
                                .foregroundStyle(Color(uiColor: .white))
                                .background(Color(uiColor: .systemBlue))
                                .cornerRadius(8.0)
                        })
                    }
                }
            }
            .padding(16)
            .background(.ultraThinMaterial)
            .cornerRadius(8.0)
            .padding(16)

        }
        .background(Color.white.opacity(0.001))
        .offset(y: status == .none ? UIScreen.main.bounds.height : .zero)
        .animation(.easeInOut, value: status)
    }
}

Solution

  • The Texts and Image, unlike the Buttons, are rendered again after the view has first rendered (onChange of status) because of the fact that they are computed values.

    Use Transition here along with Animation, this ensures that the title and description fields and the icon are animated smoothly when the status changes.

    Your PopupView should look something like this:

    var body: some View {
            Group {
                if status != .none {
                    Content
                        .transition(.move(edge: .bottom))
                }
            }
    //         .offset(y: status == .none ? UIScreen.main.bounds.height : .zero) <<< This has been removed.
            .animation(.easeInOut, value: status)
        }
        
        @ViewBuilder private var Content: some View {
            VStack {
                // ...
            }
        }
    }