0

I have two arrays.

maschineItems: [MaschineItem]

maschines: [Maschine]

Both objects have the property "name"

In my View I want to check whether the name of a maschineItem exists in the array of the machines. I want to make a 'light Bubble' for all the maschineItems which exists in machines and a 'dark Bubble' for all the maschineItems which doesnt exist in machines.

Here is my code (which works):

import SwiftUI
import Combine

struct OverviewView: View {
    
    @FetchRequest(fetchRequest: MaschineItem.getAllMaschines()) var maschineItems:FetchedResults<MaschineItem>
    @State var networkManager = NetworkManager()
    
    var body: some View {
        
        NavigationView{
            ScrollView{
                VStack(spacing: 30) {
                    ForEach(maschineItems) { maschineItem in
                        if NetworkManager.maschines.contains(where: {$0.name == maschineItem.name}) {
                            
                            BubbleView(locationText: "TEST LOCATION", textIo: "/", textnIo: "/", maschineItem: maschineItem, color: .dividerBackground, opacity: 0.25)
                            
                        }else {
                            BubbleView(locationText: "Unknown device", textIo: "/", textnIo: "/", maschineItem: maschineItem, color: .gray, opacity: 0.5)
                        }
                    }
                }
            }
            .navigationBarTitle("Übersicht", displayMode: .large)    
        }
    }

Now I want to let the Bubbles contains information about the machines. So I have to initialize the match before the if statement, isn't it? But when I do so:

    var body: some View {
        
        NavigationView{
            ScrollView{
                VStack(spacing: 30) {
                    ForEach(maschineItems) { maschineItem in
                        if let maschines = NetworkManager.maschines.first(where: {$0.name == maschineItem.name}) {
                            
                            BubbleView(locationText: "\(maschines.location.building) / \(maschines.location.workcell)", textIo: "\(maschines.resultCountiO)", textnIo: "\(maschines.location.resultCountniO)", maschineItem: maschineItem, color: .dividerBackground, opacity: 0.5)
                            
                        }else {
                            BubbleView(locationText: "Unknown device", textIo: "/", textnIo: "/", maschineItem: maschineItem, color: .gray, opacity: 0.5)
                        }
                    }
                }
            }
            .navigationBarTitle("Übersicht", displayMode: .large)   
        }
    }

I get the following error in the line of the if statement:

Closure containing control flow statement cannot be used with function builder 'ViewBuilder'

How can I fix this problem? I researched a lot and I found similar threads, which showed me that I have to use the array.first(where: {}) method, but I found nothing which helped me with this problem. And yes, I tried to make a function because logic is wrong in views. But when I write all this in a func, than the same error occurs.

I am really thankful for all who tries to help me. PS: Im German, sorry for the English :D

Sven.hig
  • 4,449
  • 2
  • 8
  • 18
  • You need to structure your data model so that it has all of the information you need. For example you could add a computed variable to your model which is an array of tuples. The tuple would contain the machine and a Boolean that you could use to control the bubble colour. – Paulw11 Jun 26 '20 at 10:40
  • @Paulw11 can you give me a code example please? I Think you didn't understand my problem. my data model has all the information I need for my bubble (name, location, etc..) -> the problem is the if statement above, I want to access the maschine whichs name is in the maschineltem array. – FamTastisch Jun 26 '20 at 10:57

1 Answers1

0

I would use a computed variable that provides the data you need, avoiding the if let. In my example I have added a computed variable (matchedMachines) which is an array of tuples. The first value in the tuple is the Machine instance. The second value is a Bool that indicates whether the machine was found or not.

//
//  ContentView.swift
//  BubbleTest
//
//  Created by Paul Wilkinson on 26/6/20.
//

import SwiftUI

struct Machine: Identifiable {
    let id = UUID()
    let name: String
    let colour: String
}

struct ContentView: View {
    
    var machines = [Machine(name: "Bob", colour: "Grey"), Machine(name:"Susan",colour: "Purple"), Machine(name: "Mary", colour: "Red"), Machine(name: "Peter", colour: "Green")]
    
    var names = ["Bob","Mary"]
    
    var matchedMachines: [(Machine,Bool)]  {
        get {
            return machines.map { ($0, names.contains($0.name))
            }
        }
    }
    
    var body: some View {
        VStack(spacing:30) {
            ForEach(matchedMachines, id: \.0.id ) { machineTuple in
                BubbleView(bubbleColor: machineTuple.1 ? .blue:.gray) {
                    Text(machineTuple.0.name)
                    Text("Location: \(machineTuple.1 ? "Test Location" : "Location Unknown")")
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

struct BubbleView<Content: View>: View {
    let content: Content
    var bubbleColor: Color
    
    init(bubbleColor: Color, @ViewBuilder content: () -> Content) {
        self.bubbleColor = bubbleColor
        self.content = content()
    }
    
    var body: some View {
        VStack {
            content
        }
        .padding()
        .background(bubbleColor)
        .cornerRadius(12.0)
    }
    
}

struct BubbleView_Previews: PreviewProvider {
    static var previews: some View {
        BubbleView(bubbleColor: .blue) {
            Text("Hello world")
        }
    }
}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • Thanks, this seems to be a good solution - i can only test it at monday next week. But i want to get the not matching maschines as well - does it work with another var notMatchedMaschines (!names.contains($0.name))? I will give you feedback again! – FamTastisch Jun 29 '20 at 12:06
  • Yes, you could use another `map` with that logic to produce an array of unmatched values – Paulw11 Jun 29 '20 at 12:20
  • Hey. My Problem is, that I first don't have an array like "names" - I just have an array with MaschineItems that contains an attribute "names" - and second that I have to loop through my maschineItems and not the machines. But when I do so, I can't get data from machines.. – FamTastisch Jul 08 '20 at 05:34
  • Ok, but it is trivial to create an array like `names` using `let names = machines.map { $0.name }` – Paulw11 Jul 08 '20 at 06:38