while working with a swiftUI playground, I came across an issue with the rendering of the view.shadow() function. where the shadow may be offset from the object it is shadowing by the distance from that object to the containing frame origin. (top left)
the following is an example showing the issue. it can be run as a macOS playground or an iOS playground, the problem is similar. but it may be more visible as an iOS playground.
when I run this, there should be 3 frames, stacked vertically, with a dot and a line rendered in each one. the shadow of the line is drawn offset down and to the right, the equivalent distance as the closest point to the top left origin. for the dot, the effect only seems to occur in the middle of the two frames. I had other graphics in the project I was working on, but they all had elements in the top left corner, therefore they rendered correctly, so I've removed them for simplicity.
import PlaygroundSupport
struct BaseStart: Shape {
var size = 10
var startPoint:InitialBase = .none
init(_ initial:InitialBase) {
startPoint = initial
}
func path(in rect: CGRect) -> Path {
let size = min(rect.size.width, rect.size.height) * 0.15
var x: CGFloat
var y: CGFloat
var path = Path()
switch startPoint {
case .firstBase:
x = 3*(rect.size.width/4)
y = 3*(rect.size.height/4)
case .secondBase :
x = 3*(rect.size.width/4)
y = (rect.size.height/4)
case .thirdBase :
x = (rect.size.width/4)
y = (rect.size.height/4)
default:
return path
}
path.addEllipse(in: CGRect(x: x-size/2, y: y-size/2, width: size, height: size))
return path
}
}
struct BaseRunner: Shape {
var runnerStartingBase:InitialBase
var advancedBy=0
init(runnerStart:InitialBase, advancedBy:Int){
runnerStartingBase = runnerStart
self.advancedBy = advancedBy
}
func path(in rect:CGRect) -> Path {
let width = rect.size.width/4
let height = rect.size.height/4
let firstBase = CGPoint(x: width*3, y: height*3)
let secondBase = CGPoint(x: width*3, y: height)
let thirdBase = CGPoint(x: width, y: height)
let home = CGPoint(x: width, y: height*3)
var path = Path()
//uncomment the following 2 lines to establish a drawn pixel in the top left corner that "fixes" the problem
// path.move(to: CGPoint(x: 0, y: 0))
// path.addLine(to: CGPoint(x:1, y: 1))
switch runnerStartingBase {
case .firstBase:
path.move(to: firstBase)
if advancedBy>0 {path.addLine(to: secondBase)}
if advancedBy>1 {path.addLine(to: thirdBase)}
if advancedBy>2 {path.addLine(to: home)}
case .secondBase:
path.move(to: secondBase)
if advancedBy>0 {path.addLine(to: thirdBase)}
if advancedBy>1 {path.addLine(to: home)}
case .thirdBase:
path.move(to: thirdBase)
if advancedBy>0 {path.addLine(to: home)}
case .none:
let _ = 0
}
return path
}
}
enum InitialBase {
case none
case firstBase
case secondBase
case thirdBase
}
public struct BatterView : View {
init(_ start:InitialBase, _ runnerPosition:Int) {
initialBase = start
self.runnerPosition = runnerPosition
}
var initialBase:InitialBase = .none
var runnerPosition:Int
mutating func advanceRunner() {
self.runnerPosition += 1
}
public var body: some View {
ZStack{
BaseRunner(runnerStart: initialBase, advancedBy: runnerPosition)
.stroke(lineWidth:3)
.foregroundColor(.black)
.shadow(color: .green, radius: 1, x: 0.2, y: 0)
BaseStart(initialBase)
.fill()
.foregroundColor(.black)
.shadow(color: .green, radius: 1, x: 2, y: 2)
//should be noted that the problem exists whether the shadow is done on the individual
//shapes or the stack as a whole.
//uncommenting the following line and commenting out the above shadows will have similar effect.
}//.shadow(color: .green, radius: 2, x: 1, y: 1)
}
}
struct Preview: View {
@State private var position = 1
func advanceRunner(){
position += 1
update.toggle()
}
@State var update = false
var body: some View {
VStack (alignment: .center, spacing: 1){
BatterView(.thirdBase, position)
.frame(minWidth: 44, idealWidth: 88, maxWidth: 88, minHeight: 44, idealHeight: 88, maxHeight: 88, alignment: .center)
BatterView(.secondBase, position)
.frame(minWidth: 44, idealWidth: 88, maxWidth: 88, minHeight: 44, idealHeight: 88, maxHeight: 88, alignment: .center)
.environment(\.colorScheme, .light)
BatterView(.firstBase, position)
.frame(minWidth: 44, idealWidth: 88, maxWidth: 88, minHeight: 44, idealHeight: 88, maxHeight: 88, alignment: .center)
.environment(\.colorScheme, .light)
Button(action: advanceRunner){
Text("run!")
}
}
}
}
PlaygroundPage.current.setLiveView(Preview()
)