#swift #swiftui
#swift #swiftui
Вопрос:
У меня есть пример структуры:
public struct Axis: Hashable, CustomStringConvertible {
public var name: String
public var description: String {
return "Axis: "(name)""
}
}
И оболочку свойств, чтобы выполнить некоторые операции над [Axis]
структурой.
@propertyWrapper
struct WrappedAxes {
var wrappedValue: [Axis] {
// This is just example, in real world it's much more complicated.
didSet {
for index in wrappedValue.indices {
var elems = Array(wrappedValue[index].name.split(separator: " "))
if elems.count>1 {
elems.removeLast()
}
let new = elems.reduce(into:"", {$0 = "($1) "})
wrappedValue[index].name = new ("(Date())")
} } } }
И я пытаюсь добавлять, вставлять и удалять Axes
в представлении SwiftUI:
public struct ContentView: View {
@Binding var axes: [Axis]
public var body: some View {
VStack {
ForEach(axes.indices, id:.self) {index in
HStack {
TextField("", text: $axes[index].name)
Button("Delete", action: {deleteAxis(index)})
Button("Insert", action: {insertAxis(index)})
}
}
Button("Add", action: addAxis)
}
}
var addAxis: () -> Void {
return {
axes.append(Axis(name: "New"))
print (axes)
}
}
var deleteAxis: (_:Int)->Void {
return {
if $0 < axes.count {
axes.remove(at: $0)
}
print (axes)
}
}
var insertAxis: (_:Int)->Void {
return {
if $0 < axes.count {
axes.insert(Axis(name: "Inserted"), at: $0)
}
print (axes)
}
}
public init (axes: Binding<[Axis]>) {
self._axes = axes
}
}
Насколько print (axes)
показывают внесенные изменения, View никогда не обновляется. Я создал очень маленькое приложение для тестирования, в котором я вызываю ContentView
:
class AppDelegate: NSObject, NSApplicationDelegate {
var window: NSWindow!
@WrappedAxes var axes = [Axis(name: "FirstOne")]
func applicationDidFinishLaunching(_ aNotification: Notification) {
let contentView = ContentView(
axes: Binding (
get: {self.axes},
set: { [self] in axes = $0}))
.... // No fancy stuff
Я открыт для любой критики самого кода и справки: как заставить это представление (и все возможные будущие подвиды) обновляться при axes
изменении?
Ответ №1:
Дело в том, что @Binding предназначен для вложенных представлений. Если вы хотите внести изменения в представление SwiftUI, вы должны использовать @State вместо того, где начинается действие. Кроме того, вам не нужно устанавливать инициализатор таким образом. Вы можете установить свое значение следующим образом, внутри ObservableObject для обработки вашей логики:
struct Axis {
var name: String
var description: String {
return "Axis: "(name)""
}
}
final class AxisViewModel: ObservableObject {
@Published var axes: [Axis] = [Axis(name: "First")]
init() { handleAxes() }
func addAxis() {
axes.append(Axis(name: "New"))
handleAxes()
}
func insertAxis(at index: Int) {
axes.insert(Axis(name: "Inserted"), at: index)
handleAxes()
}
func handleAxes() {
for index in axes.indices {
var elems = Array(axes[index].name.split(separator: " "))
if elems.count > 1 {
elems.removeLast()
}
let new = elems.reduce(into:"", { $0 = "($1) " })
axes[index].name = new ("(Date())")
}
}
}
struct ContentView: View {
@ObservedObject var viewModel = AxisViewModel()
var body: some View {
VStack {
ForEach(viewModel.axes.indices, id:.self) { index in
HStack {
TextField("", text: $viewModel.axes[index].name)
Button("Insert", action: { viewModel.insertAxis(at: index) })
}
}
Button("Add", action: viewModel.addAxis)
}
}
}
Комментарии:
1. Хорошо, это просто. Но изменится ли [Axes] за пределами представления в AppDelegate? Я думаю, это будет копия [Axes] в поле зрения, но, возможно, я ошибаюсь.
2. Ваши ‘@State var axes’ будут вносить изменения везде, где они вызываются. Вот почему вы используете @Binding во вложенных представлениях.
3. Почему вы хотите установить оси в AppDelegate?
4. Они определены там только для примера. Но они могут быть где угодно. Мне нужен пользовательский интерфейс для редактирования значений, но не оставлять их только внутри представления.
5. Вы можете создать ViewModel, используя протокол ObservableObject, для хранения значений r ‘@State’, которые можно использовать в любых представлениях. Вы можете вызвать модель представления внутри вашего представления SwiftUI, используя «@ObservedObject».
Ответ №2:
Я создал новую структуру Axes:
public struct Axes {
@WrappedAxes var _axes: [Axis]
public subscript (index: Int) -> Axis {
get { return _axes[index]}
set { _axes[index] = newValue}
}
// and all needed functions and vars to simulate Array behaviour:
public mutating func append(_ newElement: Axis ) {
_axes.append(newElement)
}
public mutating func insert(_ newElement: Axis, at index: Int ) {
_axes.insert(newElement, at: index)
}
public mutating func remove(at index: Int ) {
_axes.remove(at: index)
}
....
}
и поместите его в @ObservalbeObject
:
public class Globals: ObservableObject {
@Published public var axes: Axes
....
}
Затем определяется AxesView как
public struct AxesView: View {
@ObservedObject var globals: Globals
public var body: some View {
...
}
...
}
Вот и все. Какое-то время это работает.