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)
}
}
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.
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 {
// ...
}
}
}