5

I've made a button style to create circular neumorphic buttons in my app as follows:

struct NeumorphicCircleButtonStyle: ButtonStyle {
    var startPoint: UnitPoint
    var endPoint: UnitPoint
    var padding: CGFloat?
    var bgColor: Color?
    var bgColorOffset: Color?
    
    func makeBody(configuration: Configuration) -> some View {
        
        configuration.label
            .padding(padding ?? 30)
            .contentShape(Circle())
            .background(
                Group{
                    if configuration.isPressed {
                        Circle()
                            .fill(bgColor ?? Color.backgroundColor)
                            .overlay(
                                Circle()
                                    .stroke(Color.black.opacity(0.7), lineWidth: 4)
                                    .blur(radius: 4)
                                    .offset(x: 2, y: 2)
                                    .mask(Circle().fill(LinearGradient(colors: [Color.black, Color.clear], startPoint: startPoint, endPoint: endPoint)))
                            )
                            .overlay(
                                Circle()
                                    .stroke(bgColorOffset ?? Color.backgroundColorOffset, lineWidth: 8)
                                    .blur(radius: 4)
                                    .offset(x: -2, y: -2)
                                    .mask(Circle().fill(LinearGradient(colors: [Color.clear, Color.black], startPoint: startPoint, endPoint: endPoint)))
                            )
                    } else {
                        Circle()
                            .fill(bgColor ?? Color.backgroundColor)
                            .shadow(color: Color.black.opacity(0.25), radius: 10, x: 10, y: 10)
                            .shadow(color: bgColorOffset ?? Color.backgroundColorOffset.opacity(0.7), radius: 10, x: -5, y: -5)
                    }
                }
            )
    }
    
    // TODO: this will change the x y on the first set and need to make for other x y sets for direction, but can't find a good place for the logic, can't call it from in the makebody anywhere
    func getXoffetInnerShadow() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return 2
        case .bottomLeading:
            return -2
        case .topTrailing:
            return 2
        case .topLeading:
            return -2
        case .leading:
            return -2
        case .trailing:
            return 2
        default:
            return 0
        }
    }
    
    func getYoffsetInnerShadow() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return 2
        case .bottomLeading:
            return 2
        case .bottom:
            return 2
        case .topTrailing:
            return -2
        case .topLeading:
            return -2
        case .top:
            return -2
        default:
            return 0
        }
    }
    
    func getXoffetInnerHighlight() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return -2
        case .bottomLeading:
            return 2
        case .topTrailing:
            return -2
        case .topLeading:
            return -2
        case .leading:
            return 2
        case .trailing:
            return -2
        default:
            return 0
        }
    }
    
    func getYoffsetInnerHighlight() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return -2
        case .bottomLeading:
            return -2
        case .bottom:
            return -2
        case .topTrailing:
            return 2
        case .topLeading:
            return 2
        case .top:
            return 2
        default:
            return 0
        }
    }
    
    func getXoffsetShadow() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return 10
        case .bottomLeading:
            return -10
        case .topTrailing:
            return 10
        case .topLeading:
            return -10
        case .leading:
            return -10
        case .trailing:
            return 10
        default:
            return 0
        }
    }
    
    func getYoffsetShadow() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return 10
        case .bottomLeading:
            return 10
        case .bottom:
            return 10
        case .topTrailing:
            return -10
        case .topLeading:
            return -10
        case .top:
            return -10
        default:
            return 0
        }
    }
    
    func getXoffsetHighlight() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return -10
        case .bottomLeading:
            return 10
        case .topTrailing:
            return -10
        case .topLeading:
            return 10
        case .leading:
            return 10
        case .trailing:
            return -10
        default:
            return 0
        }
    }
    
    func getYoffsetHighlight() -> Int {
        switch endPoint {
        case .bottomTrailing:
            return -10
        case .bottomLeading:
            return -10
        case .bottom:
            return -10
        case .topTrailing:
            return 10
        case .topLeading:
            return 10
        case .top:
            return 10
        default:
            return 0
        }
    }
}


    // this is how I init my gradient if you want a working example
extension LinearGradient {
    init(_ colors: Color..., startPoint: UnitPoint, endPoint: UnitPoint) {
        self.init(gradient: Gradient(colors: colors), startPoint: startPoint, endPoint: endPoint)
    }
}

I was attempting to change the offsets of my two shadows and highlights based off the gradient endpoint passed to the style so that the inner and outer glows and shadows are always on the appropriate sides of the button. However, trying to add a switch case to the offset values or function calls to return the appropriate values spits out a Closure containing control flow statement cannot be used with result builder 'CommandsBuilder' compile time exception.

Is there any way to set the offset values of my shadows and highlights based off the UnitPoint given to the Style for direction, or do I have to make a button style for every possibility (which seems like an unfeasible expectation)?

Any and all help or suggestions is and are greatly appreciated!

theZ3r0CooL
  • 147
  • 2
  • 10
  • There is no apparent use of `CommandsBuilder` in the code you posted. On what line is the compiler flagging the error? – rob mayoff Aug 23 '21 at 21:09

1 Answers1

7

Xcode failed to produce real error that's why he says about CommandsBuilder.

Generally when you face such problem, try to comment your code block by block to localize the problem.

In this case if you comment if .. { } else { } and left both parts of if there(just to build it), Xcode will be able to tell actual problem


In this case LinearGradient doesn't have init(colors: ..., so both lines

.mask(Circle().fill(LinearGradient(colors: [Color.black, Color.clear], startPoint: startPoint, endPoint: endPoint)))

Should be replaced with:

.mask(Circle().fill(LinearGradient(gradient: Gradient(colors: [Color.black, Color.clear]), startPoint: startPoint, endPoint: endPoint)))

If you wanna use defined below functions, like getXoffetInnerShadow, at a parameter to .offset modifier, you should replace return type to CGFloat, because that's the only type .offset accepts.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • My apologies. The block of code I posted works absolutely fine. I'm using an extension on linear gradient for that, which I'll update my question with. The actual question is how can I set the values of `x:` and `y:` in my offsets using the functions I've defined below my makeBody? Or is there anyway to set those vars based off of the direction of the gradient I make with the start and end UnitPoints? – theZ3r0CooL Aug 24 '21 at 04:54
  • @TheN3wbie `.offset(x:, y:)` cannot accept `Int`, just replace return type to `CGFloat` – Phil Dukhov Aug 24 '21 at 04:58
  • I tried wrapping the calls in `CGFloat()`, but that didn't work can you not cast Int to CGFloat? – theZ3r0CooL Aug 24 '21 at 05:01
  • @TheN3wbie If you comment `if .. {` `} else {` `}` and left both parts of `if` there(just to build it), Xcode will be able to tell actual problem. – Phil Dukhov Aug 24 '21 at 05:04
  • @TheN3wbie wrapping should work, but why don't you change the return type of your functions? It's much cleaner – Phil Dukhov Aug 24 '21 at 05:05
  • Marking as accepted because changing the functions to return `CGFloat` does resolve the issue. Xcode could use better error reporting. – theZ3r0CooL Aug 24 '21 at 05:11