Как передать объект данных между представлениями, чтобы его значения могли быть изменены? - PullRequest
0 голосов
/ 04 августа 2020

Я создал объект, который представляет текущее состояние рисования:

class ColoringImageViewModel: ObservableObject {
    @Published var shapeItemsByKey = [UUID: ShapeItem]()
    var shapeItemKeys: [UUID] = []
    var scale: CGFloat = 0
    var offset: CGSize = CGSize.zero
    var dragGestureMode: DragGestureEnum = DragGestureEnum.FillAreas
    @Published var selectedColor: Color?
    var selectedImage: String?
    
    init(selectedImage: String) {
        let svgURL = Bundle.main.url(forResource: selectedImage, withExtension: "svg")!
        let _paths = SVGBezierPath.pathsFromSVG(at: svgURL)
        for (index, path) in _paths.enumerated() {
            let scaledBezier = ScaledBezier(bezierPath: path)
            let shapeItem = ShapeItem(path: scaledBezier)
            shapeItemsByKey[shapeItem.id] = shapeItem
            shapeItemKeys.append(shapeItem.id)
        }
    }
}

Основное представление состоит из нескольких представлений - одно для изображения и одно для цветовой палитры среди других:

struct ColoringScreenView: View {
    @ObservedObject var coloringImageViewModel : ColoringImageViewModel = ColoringImageViewModel(selectedImage: "tiger")
    var body: some View {
        VStack {
            ColoringImageView(coloringImageViewModel: coloringImageViewModel)
            ColoringImageButtonsView(coloringImageViewModel: coloringImageViewModel)
        }
    }
}

ColoringImageButtonsView должен изменять выбранный цвет в зависимости от выбранного цвета:

import SwiftUI

struct ColoringImageButtonsView: View {
    @ObservedObject var coloringImageViewModel : ColoringImageViewModel
    var paletteColors: [PaletteColorItem] = [PaletteColorItem(color: .red), PaletteColorItem(color: .green), PaletteColorItem(color: .blue), PaletteColorItem(color: .yellow), PaletteColorItem(color: .purple), PaletteColorItem(color: .black), PaletteColorItem(color: .red), PaletteColorItem(color: .red), PaletteColorItem(color: .red)]
    var body: some View {
        HStack {
            ForEach(paletteColors) { colorItem in
                Button("blue", action: {
                    self.coloringImageViewModel.selectedColor = colorItem.color
                    print("Selected color: \(self.coloringImageViewModel.selectedColor)")
                }).buttonStyle(ColorButtonStyle(color: colorItem.color))
            }
        }
    }
}

struct ColorButtonStyle: ButtonStyle {
    var color: Color
    init(color: Color) {
        self.color = color
    }
    func makeBody(configuration: Configuration) -> some View {
        Circle()
            .fill(color)
            .frame(width: 40, height: 40, alignment: .top)
    }
}

struct ColoringImageButtonsView_Previews: PreviewProvider {
    static var previews: some View {
        var coloringImageViewModel : ColoringImageViewModel = ColoringImageViewModel(selectedImage: "tiger")
        ColoringImageButtonsView(coloringImageViewModel: coloringImageViewModel)
    }
}

В ShapeView (subview ImageView) он видит, что colorImageViewModel.selectedColor всегда равен нулю:

struct ShapeView: View {
    var id: UUID
    @Binding var coloringImageViewModel : ColoringImageViewModel
    var body: some View {
        ZStack {
            var shapeItem = coloringImageViewModel.shapeItemsByKey[id]!
            shapeItem.path
                .fill(shapeItem.color)
                .gesture(
                    DragGesture(minimumDistance: 0, coordinateSpace: .global)
                        .onChanged { gesture in
                            print("Tap location: \(gesture.startLocation)")
                            guard let currentlySelectedColor = coloringImageViewModel.selectedColor else {return}
                            shapeItem.color = currentlySelectedColor
                        }
                )
                .allowsHitTesting(coloringImageViewModel.dragGestureMode == DragGestureEnum.FillAreas)
            shapeItem.path.stroke(Color.black)
        }
    }
}

Я читал о @Binding, @State и @ObservedObject, но мне не удалось правильно использовать оболочки свойств, чтобы удерживать состояния в одном экземпляре объекта (ColoringImageViewModel) и изменять / передавать его значения среди нескольких представлений. Кто-нибудь знает, как правильно это сделать?

Ответы [ 2 ]

1 голос
/ 04 августа 2020

Я сделал Swift Playground, как мне кажется, с упрощенной версией вашей проблемы. Он показывает, как вы можете использовать @ObservedObject, @EnvironmentObject, @State и @Binding в зависимости от контекста для достижения вашей цели.

Если вы запустите его, вы должны увидеть что-то вроде этого:

Playground in action

Notice in the code below how the instance of ColoringImageViewModel is actually created outside of any views so that it does not get caught in the view's lifecycle.

Also check out the comments next to each piece of state data that explain the different usage scenarios.

import SwiftUI
import PlaygroundSupport

// Some global constants
let images = ["circle.fill", "triangle.fill", "square.fill"]
let colors: [Color] = [.red, .green, .blue]

/// Simplified model
class ColoringImageViewModel: ObservableObject {
    
    @Published var selectedColor: Color?
    
    // Use singleton pattern to manage instance outside view hierarchy
    static let shared = ColoringImageViewModel()
}

/// Entry-point for the coloring tool
struct ColoringTool: View {
    
    // We bring 
    @ObservedObject var model =  ColoringImageViewModel.shared
    
    var body: some View {
        VStack {
            ColorPalette(selection: $model.selectedColor)
            // We pass a binding only to the color selection
            
            CanvasDisplay()
            .environmentObject(model)
            // Inject model into CanvasDisplay's environment
            
            Text("Tap on an image to color it!")
        }
    }
}

struct ColorPalette: View {
    
    // Bindings are parameters that NEED to be modified
    @Binding var selection: Color?
    
    var body: some View {
        HStack {
            Text("Select a color:")
            ForEach(colors, id: \.self) { color in
                Rectangle()
                .frame(width: 50, height: 50)
                .foregroundColor(color)
                .border(Color.white, width:
                    color == self.selection ? 3 : 0
                )
                .onTapGesture {
                    self.selection = color
                }
            }
        }
    }
}

/// Displays all images
struct CanvasDisplay: View {
    
    // Environment objects are injected by some ancestor
    @EnvironmentObject private var model: ColoringImageViewModel
    
    var body: some View {
        HStack {
            ForEach(images, id: \.self) {
                ImageDisplay(imageName: $0, selectedColor: self.model.selectedColor)
            }
        }
    }
}

/// A single colored, tappable image
struct ImageDisplay: View {
    
    let imageName: String // Constant parameter
    let selectedColor: Color? // Constant parameter
    @State private var imageColor: Color? // Internal variable state
    
    var body: some View {
        Image(systemName: imageName)
        .resizable()
        .aspectRatio(contentMode: .fit)
        .foregroundColor(
            imageColor == nil ? nil : imageColor!
        )
        .onTapGesture {
            self.imageColor = self.selectedColor
        }
    }
}

PlaygroundPage.current.setLiveView(ColoringTool())

0 голосов
/ 04 августа 2020

Вы не показываете, где вы используете ShapeView в ImageView, но с учетом logi c другой предоставленной модели кода должно быть ObservedObject

struct ShapeView: View {
    var id: UUID
    @ObservedObject var coloringImageViewModel : ColoringImageViewModel

    // ... other code
...