0

For each date category, there may be one or more categories, and for each category, there may be one or more financial transactions. These categories are represented in a circle form, similar to a bubble chart. However, I am experiencing issues with the CircleItem view as it crashes, and CategoryView is not functioning as intended. The goal is to have each category represented as a clickable circle, and to ensure that newly created circles do not overlap with existing ones. As this is my first app, I am unsure of how to proceed with resolving these issues.

// for each dateCategory there can be one ore more Category and for each Category there can be one or more FinancialTransaction

struct DateCategory {
    let date: Date
    var categories: [Category]
}
class Category: Identifiable {
    let id = UUID()
    var categoryName: String
    var transactions: [FinancialTransaction]
    var size: CGFloat
    var offset = CGSize.zero
    
    init(categoryName: String, transactions: [FinancialTransaction], size: CGFloat, offset: CGSize) {
        self.categoryName = categoryName
        self.transactions = transactions
        self.size = size
        self.offset = offset
    }
}

   
    

struct FinancialTransaction: Identifiable {
    let id = UUID()
    var amount: Int
    var name: String
    var date: Date
    
    init(amount: Int, name: String, date: Date) {
        self.amount = amount
        self.name = name
        self.date = date
    }
}

class CategoryEnvironment: ObservableObject {
    @Published var categories: [Category] = []

    }

class TransactionsEnvironment: ObservableObject {
    @Published var transactions: [FinancialTransaction] = []
  }



struct CategoryView: View {
    @Namespace var namespace
    @State var show = false
    @EnvironmentObject var categories: CategoryEnvironment
    @State var selectedCategory: Category?
    @State var selectedId = UUID()

   

    var body: so
me View {
        ZStack {
            NavigationView {
                VStack{
                    //MARK: The catefories
                    
                    
                    if !show {
                        ForEach(categories.categories) { category in
                            CiercleItem(namespace: namespace, spacing: 0, startAngle: 180, clockwise: true)
                                    .onTapGesture {
                                        withAnimation(.spring(response: 1, dampingFraction: 1, blendDuration: 1)){
                                            show.toggle()
                                            selectedId = category.id
                                        }
                                    
                            }
                        }
                    }
                    if show {
                        ForEach(categories.categories) { category in
                        if  category.id == selectedId {
                                CiercleView(namespace: namespace, show: $show)
                                .zIndex(1)
                               .transition(.asymmetric(insertion: .opacity.animation(.easeInOut(duration: 0.1)), removal: .opacity.animation(.easeInOut(duration: 0.3).delay(0.3))))
                            }
                        }
                    }
               
                    
                } 
                    .navigationBarItems(trailing:
                        NavigationLink(destination: NewCategoryView()) {
                            Image(systemName: "plus")
                        }
                    )
                
            }
            
            
        }
                    
                  
            
              }
          }
      
struct CiercleItem: View {
  
    var namespace: Namespace.ID
    @EnvironmentObject var categories: CategoryEnvironment
    @State var selectedCategory: Category?
    
    // Spacing between bubbles
      var spacing: CGFloat
      
      // startAngle in degrees -360 to 360 from left horizontal
      var startAngle: Int
      
      // direction
      var clockwise: Bool
      
      struct ViewSize {
          var xMin: CGFloat = 0
          var xMax: CGFloat = 0
          var yMin: CGFloat = 0
          var yMax: CGFloat = 0
      }
    @State private var mySize = ViewSize()
    
    
    var body: some View {
        
        let xSize = (mySize.xMax - mySize.xMin) == 0 ? 1 : (mySize.xMax - mySize.xMin)
        let ySize = (mySize.yMax - mySize.yMin) == 0 ? 1 : (mySize.yMax - mySize.yMin)

        
        GeometryReader { geometry in
            let xScale = geometry.size.width / xSize
                       let yScale = geometry.size.height / ySize
                       let scale = min(xScale, yScale)
            
            ZStack {
                ForEach(categories.categories.indices, id: \.self) { index in
                    ZStack {
                        Circle()
                            .fill(Color.black)
                            .matchedGeometryEffect(id: "background", in: namespace)
                            .frame(width: CGFloat(self.categories.categories[index].size) * scale, height: CGFloat(self.categories.categories[index].size) * scale)
                            .overlay(
                               VStack{
                                
                                    Text("-50")
                                        .foregroundColor(.white)
                                        .font(.largeTitle)
                                        .fontWeight(.light)
                                        .padding(2)
                                    
                                    Text(self.categories.categories[index].categoryName)
                                        .foregroundColor(.white)
                                        .font(.body)
                                        .fontWeight(.light)
                                        .onTapGesture {
                                            self.selectedCategory = self.categories.categories[index]
                                        }
                                }
                    )
                    }.offset(x: self.categories.categories[index].offset.width * scale, y: self.categories.categories[index].offset.height * scale)
                }
            }.offset(x: xOffset() * scale, y: yOffset() * scale)
                
        } .onAppear {
            setOffets()
            mySize = absoluteSize()
        }
    }
    
    
    // taken out of main for compiler complexity issue
    func xOffset() -> CGFloat {
        let size = categories.categories[0].size
        let xOffset = mySize.xMin + size / 2
        return -xOffset
    }
    
    func yOffset() -> CGFloat {
        let size = categories.categories[0].size
        let yOffset = mySize.yMin + size / 2
        return -yOffset
    }
    // calculate and set the offsets
        func setOffets() {
            if categories.categories.isEmpty { return }
            // first circle
            categories.categories[0].offset = CGSize.zero
            
            if categories.categories.count < 2 { return }
            // second circle
            let b = (categories.categories[0].size + categories.categories[1].size) / 2 + spacing
            
            // start Angle
            var alpha: CGFloat = CGFloat(startAngle) / 180 * CGFloat.pi
            
            categories.categories[1].offset = CGSize(width:  cos(alpha) * b,
                                    height: sin(alpha) * b)
            
            // other circles
            for i in 2..<categories.categories.count {
                
                // sides of the triangle from circle center points
                let c = (categories.categories[0].size + categories.categories[i-1].size) / 2 + spacing
                let b = (categories.categories[0].size + categories.categories[i].size) / 2 + spacing
                let a = (categories.categories[i-1].size + categories.categories[i].size) / 2 + spacing
                
                alpha += calculateAlpha(a, b, c) * (clockwise ? 1 : -1)
                
                let x = cos(alpha) * b
                let y = sin(alpha) * b
                
                categories.categories[i].offset = CGSize(width: x, height: y )
            }
        }
        
        // Calculate alpha from sides - 1. Cosine theorem
        func calculateAlpha(_ a: CGFloat, _ b: CGFloat, _ c: CGFloat) -> CGFloat {
            return acos(
                ( pow(a, 2) - pow(b, 2) - pow(c, 2) )
                /
                ( -2 * b * c ) )
            
        }
        
        // calculate max dimensions of offset view
        func absoluteSize() -> ViewSize {
            let radius = categories.categories[0].size / 2
            let initialSize = ViewSize(xMin: -radius, xMax: radius, yMin: -radius, yMax: radius)
            
            let maxSize = categories.categories.reduce(initialSize, { partialResult, item in
                let xMin = min(
                    partialResult.xMin,
                    item.offset.width - item.size / 2 - spacing
                )
                let xMax = max(
                    partialResult.xMax,
                    item.offset.width + item.size / 2 + spacing
                )
                let yMin = min(
                    partialResult.yMin,
                    item.offset.height - item.size / 2 - spacing
                )
                let yMax = max(
                    partialResult.yMax,
                    item.offset.height + item.size / 2 + spacing
                )
                return ViewSize(xMin: xMin, xMax: xMax, yMin: yMin, yMax: yMax)
            })
            return maxSize
        }
        
    }

this is a lick of an image perhaps it will give you an idea what I am trying to make hear enter image description here

burnsi
  • 6,194
  • 13
  • 17
  • 27

0 Answers0