SwiftUI ViewModifier для настраиваемого просмотра

Есть ли способ создать модификатор для обновления @State private var в изменяемом виде?

У меня есть настраиваемое представление, которое возвращает либо Text с "динамическим" цветом фона ИЛИ Circle с "динамическим" цветом переднего плана.

       struct ChildView: View {
    var theText = ""
    
    @State private var color = Color(.purple)
    
    var body: some View {
        HStack {
            if theText.isEmpty {          // If there's no theText, a Circle is created
                Circle()
                    .foregroundColor(color)
                    .frame(width: 100, height: 100)
            } else {                      // If theText is provided, a Text is created
                Text(theText)
                    .padding()
                    .background(RoundedRectangle(cornerRadius: 25.0)
                                    .foregroundColor(color))
                    .foregroundColor(.white)
            }
        }
    }
}

Я повторно использую это представление в разных частях своего приложения. Как видите, единственный параметр, который мне нужно указать, - это theText. Итак, возможные способы создания этого ChildView следующие:

       struct SomeParentView: View {
    var body: some View {
        VStack(spacing: 20) {
            ChildView()   // <- Will create a circle

            ChildView(theText: "Hello world!")   // <- Will create a text with background
        }
    }
}

Пока ничего особенного. Теперь мне нужно создать (возможно) модификатор или что-то подобное, чтобы в родительских представлениях я мог изменить значение этого @State private var color из .redна другой цвет, если мне нужна дополнительная настройка этого ChildView. Пример того, чего я пытаюсь достичь:

       struct SomeOtherParentView: View {
    var body: some View {
        HStack(spacing: 20) {
            ChildView()

            ChildView(theText: "Hello world!")
                .someModifierOrTheLike(color: Color.green)   // <- what I think I need
        }
    }
}

Я знаю, что могу просто удалить private ключевое слово из этого var и пройти color как параметр в конструкторе (например: ChildView(theText: "Hello World", color: .green)), но я не думаю, что это способ решить эту проблему, потому что, если мне понадобится дополнительная настройка дочернего представления, я бы получил очень большой конструктор.

Итак, есть идеи о том, как достичь того, что я ищу? Надеюсь, я объяснил:) Спасибо!!!

3 ответа

Решение

Это ваше представление, а модификаторы - это просто функции, которые генерируют другое, измененное представление, так что... вот несколько возможных простых способов добиться того, чего вы хотите.

Протестировано с Xcode 12 / iOS 14

       struct ChildView: View {
    var theText = ""
    
    @State private var color = Color(.purple)
    
    var body: some View {
        HStack {
            if theText.isEmpty {          // If there's no theText, a Circle is created
                Circle()
                    .foregroundColor(color)
                    .frame(width: 100, height: 100)
            } else {                      // If theText is provided, a Text is created
                Text(theText)
                    .padding()
                    .background(RoundedRectangle(cornerRadius: 25.0)
                                    .foregroundColor(color))
                    .foregroundColor(.white)
            }
        }
    }
    
    // simply modify self, as self is just a value
    public func someModifierOrTheLike(color: Color) -> some View {
        var view = self
        view._color = State(initialValue: color)
        return view.id(UUID())
    }
}

Использование настраиваемого модификатора ViewModifier действительно является способом помочь предоставить пользователям более простой интерфейс, но общая идея того, как передать параметры настройки в представление (кроме использования init), осуществляется через переменные среды с .environment.

struct MyColorKey: EnvironmentKey {
   static var defaultValue: Color = .black
}

extension EnvironmentValues {
   var myColor: Color {
      get { self[MyColorKey] }
      set { self[MyColorKey] = newValue }
   }
}

Тогда вы можете положиться на это в своем представлении:

struct ChildView: View {
   @Environment(\.myColor) var color: Color

   var body: some View {
      Circle()
         .foregroundColor(color)
         .frame(width: 100, height: 100)
   }
}

И использование будет:

ChildView()
   .environment(\.myColor, .blue)

Вы можете сделать его немного лучше, используя модификатор вида:

struct MyColorModifier: ViewModifier {
   var color: Color

   func body(content: Content) -> some View {
      content
         .environment(\.myColor, color)
   }
}

extension ChildView {
   func myColor(_ color: Color) { 
      self.modifier(MyColorModifier(color: color) 
   }
}

ChildView()
   .myColor(.blue)

Конечно, если у вас есть несколько параметров настройки или если это слишком низкий уровень для пользователя, вы можете создать ViewModifier, который предоставляет их подмножество, или создать тип, который инкапсулирует стиль, как SwiftUI делает с .buttonStyle(_:)

Вот как вы можете связать методы на основе ответа Аспери:

      struct ChildView: View {
    @State private var foregroundColor = Color.red
    @State private var backgroundColor = Color.blue

    var body: some View {
        Text("Hello World")
            .foregroundColor(foregroundColor)
            .background(backgroundColor)
    }

    func foreground(color: Color) -> ChildView {
        var view = self
        view._foregroundColor = State(initialValue: color)
        return view
    }

    func background(color: Color) -> ChildView {
        var view = self
        view._backgroundColor = State(initialValue: color)
        return view
    }
}

struct ParentView: View {
    var body: some View {
        ChildView()
            .foreground(color: .yellow)
            .background(color: .green)
            .id(UUID())
    }
}
Другие вопросы по тегам