0

So I have a GeometryReader encapsulating most of my contentView,

struct ContentView: View {
    var body: some View {
        GeometryReader { proxy in 
            ....
        }
    }
}

but I'm running into an issue with it. I pass it into various views in my ContentView with no issues. For some reason though, if I try to pass it into a specific set of views defined in if statements in my app, I get the error "The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions". The problem seems to stem from adding more variables (proxy, proxyOffsetTop, or proxyOffsetBottom) to this view (no idea why):

import Foundation
import SwiftUI
import Mapbox
struct SlideOverCard<Content: View>: View {
@ObservedObject var keyboardResponder = KeyboardResponder()
@GestureState private var dragState = DragState.inactive
@Binding var feedbackSubmitted: Bool
@Binding var position: CardPosition
var mapStyle: URL
//let proxy: GeometryProxy
var content: () -> Content
//    var proxyOffsetTop: CGFloat
//    var proxyOffsetBottom: CGFloat
var body: some View {
    let drag = DragGesture()
        .updating($dragState) { drag, state, transaction in
            state = .dragging(translation: drag.translation)
    }.onEnded(onDragEnded)

    if (self.position == CardPosition.bottom(UIScreen.main.bounds.height - (77.5)) || self.position == CardPosition.middle(UIScreen.main.bounds.height - (135))) && mapStyle == MGLStyle.outdoorsStyleURL {
        return VStack (spacing: 0) {
            Handle(mapStyle: self.mapStyle)
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height)
        .background(Color.white)
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position.offset + self.dragState.translation.height - keyboardResponder.currentHeight)
        .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }
        
    else if self.position == CardPosition.bottom(UIScreen.main.bounds.height - (77.5)) || self.position == CardPosition.middle(UIScreen.main.bounds.height - (135)) && mapStyle == MGLStyle.darkStyleURL {
        return VStack (spacing: 0) {
            Handle(mapStyle: self.mapStyle)
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height)
        .background(Color.init(red: 15/255, green: 15/255, blue: 15/255))
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position.offset + self.dragState.translation.height - keyboardResponder.currentHeight)
        .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }
    
    else if mapStyle == MGLStyle.darkStyleURL {
        return VStack (spacing: 0) {
            Handle(mapStyle: self.mapStyle)
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height)
        .background(Color.init(red: 15/255, green: 15/255, blue: 15/255))
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position.offset + self.dragState.translation.height)
        .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }
    
    else {
        return VStack (spacing: 0) {
            Handle(mapStyle: self.mapStyle)
            self.content()
        }
        .frame(height: UIScreen.main.bounds.height)
        .background(Color.white)
        .cornerRadius(10.0)
        .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
        .offset(y: self.position.offset + self.dragState.translation.height)
        .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
        .gesture(drag)
    }
}

private func onDragEnded(drag: DragGesture.Value) {
    let verticalDirection = drag.predictedEndLocation.y - drag.location.y
    let cardTopEdgeLocation = self.position.offset + drag.translation.height
    let positionAbove: CardPosition
    let positionBelow: CardPosition
    let closestPosition: CardPosition
    if cardTopEdgeLocation <= CardPosition.middle(UIScreen.main.bounds.height - (135)).offset {
        positionAbove = .top(50)
        positionBelow = .middle(UIScreen.main.bounds.height - (135))
        self.feedbackSubmitted = false
    } else {
        positionAbove = .middle(UIScreen.main.bounds.height - (135))
        positionBelow = .bottom(UIScreen.main.bounds.height - (77.5))
        self.feedbackSubmitted = false
    }
    if (cardTopEdgeLocation - positionAbove.offset) < (positionBelow.offset - cardTopEdgeLocation) {
        closestPosition = positionAbove
    } else {
        closestPosition = positionBelow
    }
    if verticalDirection > 0 {
        self.position = positionBelow
    } else if verticalDirection < 0 {
        self.position = positionAbove
    } else {
        self.position = closestPosition
    }
}
}

enum RelativeCardPosition {
case top
case middle
case bottom
}

struct CardPosition: Equatable {
let relativeCardPosition: RelativeCardPosition
let offset: CGFloat

static func top(_ offset: CGFloat) -> CardPosition {
    CardPosition(relativeCardPosition: .top, offset: offset)
}

static func middle(_ offset: CGFloat) -> CardPosition {
    CardPosition(relativeCardPosition: .middle, offset: offset)
}

static func bottom(_ offset: CGFloat) -> CardPosition {
    CardPosition(relativeCardPosition: .bottom, offset: offset)
}
}

enum DragState {
case inactive
case dragging(translation: CGSize)
var translation: CGSize {
    switch self {
    case .inactive:
        return .zero
    case .dragging(let translation):
        return translation
    }
}
var isDragging: Bool {
    switch self {
    case .inactive:
        return false
    case .dragging:
        return true
    }
}
}

As soon as I remove the proxy from the view I am passing it into, the app runs fine. I tried wrapping different views in groups because I figured maybe I hit that 10-view cap but I'm pretty sure that's not the error anymore...I have no idea what could be causing this, any suggestions?

4

1 回答 1

1

The compiler's task here is to figure out the resulting view type. And it is really a tough task here considering that the code has a lot of different cases and each case can return a different view type. I'd suggest doing a small refactoring of this code. As we can see' there are only two parameters that depend on the calculations. So:

  1. Lets create a structure to store these parameters
private struct ContentParameters {
    var backgroundColor: Color
    var offsetY: CGFloat
}
  1. Lets move all the calculations into a separate method that returns a result (in our case variable also works):
private var contentParameters: ContentParameters {
    if (self.position == CardPosition.bottom(UIScreen.main.bounds.height - (77.5)) || self.position == CardPosition.middle(UIScreen.main.bounds.height - (135))) && mapStyle == MGLStyle.outdoorsStyleURL {
        return ContentParameters(
            backgroundColor: .white,
            offsetY: self.position.offset + self.dragState.translation.height - keyboardResponder.currentHeight
        )
    } else if self.position == CardPosition.bottom(UIScreen.main.bounds.height - (77.5)) || self.position == CardPosition.middle(UIScreen.main.bounds.height - (135)) && mapStyle == MGLStyle.darkStyleURL {
        return ContentParameters(
            backgroundColor: Color(red: 15/255, green: 15/255, blue: 15/255),
            offsetY: self.position.offset + self.dragState.translation.height - keyboardResponder.currentHeight
        )
    } else if mapStyle == MGLStyle.darkStyleURL {
        return ContentParameters(
            backgroundColor: Color(red: 15/255, green: 15/255, blue: 15/255),
            offsetY: self.position.offset + self.dragState.translation.height
        )
    } else {
        return ContentParameters(
            backgroundColor: .white,
            offsetY: self.position.offset + self.dragState.translation.height
        )
    }
}

Now, as we have moved all the complex calculations away, lets clean the code a bit by using that:

var body: some View {
    let drag = DragGesture()        // this part stays the same
        .updating($dragState) { drag, state, transaction in
            state = .dragging(translation: drag.translation)
        }.onEnded(onDragEnded)
        
    let parameters = contentParameters // storing in a constant to not to calculate multiple times
        
    return VStack (spacing: 0) {       // using the real content from the original code
        Handle(mapStyle: mapStyle)
        content()
    }
    // Applying all the modifiers from the original code
    .frame(height: UIScreen.main.bounds.height)
    .background(parameters.backgroundColor)    // using background color that was already calculated
    .cornerRadius(10.0)
    .shadow(color: Color(.sRGBLinear, white: 0, opacity: 0.13), radius: 10.0)
    .offset(y: parameters.offsetY)             // using the offset that was alredy calcualted
    .animation(self.dragState.isDragging ? nil : .interpolatingSpring(stiffness: 300.0, damping: 30.0, initialVelocity: 10.0))
    .gesture(drag)
}

I believe that this will solve the problem – the resulted code is so much easier to understand for the compiler. As an additional benefit, we get more SwiftUI animation friendly code (in case if you decide to add animations support later).

于 2020-09-25T00:47:16.927 回答