I have the following code:
import SwiftUI
enum OptionButtonRole {
case normal
case destructive
case cancel
}
struct OptionButton {
var title: String
var role: OptionButtonRole
let id = UUID()
var action: () -> Void
}
struct OptionSheet: ViewModifier {
@State var isPresented = true
var title: String
var buttons: [OptionButton]
init(title: String, buttons: OptionButton...) {
self.title = title
self.buttons = buttons
}
func body(content: Content) -> some View {
content
.confirmationDialog(title,
isPresented: $isPresented,
titleVisibility: .visible) {
ForEach(buttons, id: \.title) { button in
let role: ButtonRole? = button.role == .normal ? nil : button.role == .destructive ? .destructive : .cancel
Button(button.title, role: role, action: button.action)
}
}
}
}
It builds and my app shows the option sheet with the specified buttons.
However, if I use an alternative Button.init
, i.e. if I replace the body with the following code:
func body(content: Content) -> some View {
content
.confirmationDialog(title,
isPresented: $isPresented,
titleVisibility: .visible) {
ForEach(buttons, id: \.title) { button in
let role: ButtonRole? = button.role == .normal ? nil : button.role == .destructive ? .destructive : .cancel
Button(role: role, action: button.action) {
Text(button.title)
}
}
}
}
Then, Xcode hangs on build with the following activity:
Is there an error in my code or is this a compiler bug (Xcode Version 14.1 (14B47b))?
CodePudding user response:
While your code is technically correct, the ability of view logic to evaluate variable values can get quite compiler-intensive, especially when you have multiple chained ternaries and logic inside a ForEach
(which seems to make a bigger difference than one would probably think).
I'd be tempted to move the conditional logic outside the loop altogether, so that you're calling a method rather than needing to evaluate and store a local variable. You could make this a private func
in your view, or as an extension to your custom enum. For example:
extension OptionButtonRole {
var buttonRole: ButtonRole? {
switch self {
case .destructive: return .destructive
case .cancel: return .cancel
default: return nil
}
}
}
// in your view
ForEach(buttons, id: \.title) { button in
Button(role: button.role.buttonRole, action: button.action) {
Text(button.title)
}
}