Skip to content

Commit c954ce3

Browse files
committed
Fix shadow on iOS 26
1 parent 52b0f7f commit c954ce3

2 files changed

Lines changed: 113 additions & 3 deletions

File tree

Sources/Extensions.swift

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,73 @@ extension UIBezierPath {
214214
cornerRadii: CGSize(width: cornerRadius,
215215
height: cornerRadius))
216216
}
217+
218+
#if compiler(>=6.2)
219+
@available(iOS 26.0, *)
220+
static func path(roundedRect rect: CGRect, view: UIView) -> UIBezierPath {
221+
// Apply inset to hide the gap between UIBezierPath's circular arcs
222+
// and UICornerConfiguration's corner curves.
223+
let rect = rect.insetBy(dx: 4, dy: 4)
224+
225+
// Query the effective corner radius from the view using the new iOS 26 API
226+
// This properly handles UICornerConfiguration including .fixed, .dynamic, and .continuous
227+
let topLeadingRadius = view.effectiveRadius(corner: .topLeft)
228+
let topTrailingRadius = view.effectiveRadius(corner: .topRight)
229+
let bottomLeadingRadius = view.effectiveRadius(corner: .bottomLeft)
230+
let bottomTrailingRadius = view.effectiveRadius(corner: .bottomRight)
231+
232+
// Otherwise, create a path with individual corner radii
233+
let path = UIBezierPath()
234+
let minX = rect.minX
235+
let minY = rect.minY
236+
let maxX = rect.maxX
237+
let maxY = rect.maxY
238+
239+
// Start from top left, after the corner
240+
path.move(to: CGPoint(x: minX + topLeadingRadius, y: minY))
241+
242+
// Top edge and top-right corner
243+
path.addLine(to: CGPoint(x: maxX - topTrailingRadius, y: minY))
244+
if topTrailingRadius > 0 {
245+
path.addArc(withCenter: CGPoint(x: maxX - topTrailingRadius, y: minY + topTrailingRadius),
246+
radius: topTrailingRadius,
247+
startAngle: -0.5 * .pi,
248+
endAngle: 0,
249+
clockwise: true)
250+
}
251+
252+
// Right edge and bottom-right corner
253+
path.addLine(to: CGPoint(x: maxX, y: maxY - bottomTrailingRadius))
254+
if bottomTrailingRadius > 0 {
255+
path.addArc(withCenter: CGPoint(x: maxX - bottomTrailingRadius, y: maxY - bottomTrailingRadius),
256+
radius: bottomTrailingRadius,
257+
startAngle: 0,
258+
endAngle: .pi * 0.5,
259+
clockwise: true)
260+
}
261+
262+
// Bottom edge and bottom-left corner
263+
path.addLine(to: CGPoint(x: minX + bottomLeadingRadius, y: maxY))
264+
if bottomLeadingRadius > 0 {
265+
path.addArc(withCenter: CGPoint(x: minX + bottomLeadingRadius, y: maxY - bottomLeadingRadius),
266+
radius: bottomLeadingRadius,
267+
startAngle: .pi * 0.5,
268+
endAngle: .pi,
269+
clockwise: true)
270+
}
271+
272+
// Left edge and top-left corner
273+
path.addLine(to: CGPoint(x: minX, y: minY + topLeadingRadius))
274+
if topLeadingRadius > 0 {
275+
path.addArc(withCenter: CGPoint(x: minX + topLeadingRadius, y: minY + topLeadingRadius),
276+
radius: topLeadingRadius,
277+
startAngle: .pi,
278+
endAngle: .pi * 1.5,
279+
clockwise: true)
280+
}
281+
282+
path.close()
283+
return path
284+
}
285+
#endif
217286
}

Sources/SurfaceView.swift

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,23 @@ public class SurfaceView: UIView {
359359

360360
let spread = shadow.spread
361361
let shadowRect = containerView.frame.insetBy(dx: -spread, dy: -spread)
362-
let shadowPath = UIBezierPath.path(roundedRect: shadowRect,
362+
363+
// Create shadow path based on corner configuration or corner radius
364+
let shadowPath: UIBezierPath
365+
#if compiler(>=6.2)
366+
if #available(iOS 26.0, *), appearance.cornerConfiguration != nil {
367+
// Use UIView.effectiveRadius(corner:) API for iOS 26+
368+
// This properly queries the actual corner radius from UICornerConfiguration
369+
shadowPath = UIBezierPath.path(roundedRect: shadowRect, view: containerView)
370+
} else {
371+
shadowPath = UIBezierPath.path(roundedRect: shadowRect,
363372
appearance: appearance)
373+
}
374+
#else
375+
shadowPath = UIBezierPath.path(roundedRect: shadowRect,
376+
appearance: appearance)
377+
#endif
378+
364379
shadowLayer.shadowPath = shadowPath.cgPath
365380
shadowLayer.shadowColor = shadow.color.cgColor
366381
shadowLayer.shadowOffset = shadow.offset
@@ -369,17 +384,43 @@ public class SurfaceView: UIView {
369384
shadowLayer.shadowOpacity = shadow.opacity
370385

371386
let mask = CAShapeLayer()
372-
let path = UIBezierPath.path(roundedRect: containerView.frame,
373-
appearance: appearance)
387+
388+
// Create mask path based on corner configuration or corner radius
389+
let path: UIBezierPath
390+
#if compiler(>=6.2)
391+
if #available(iOS 26.0, *), appearance.cornerConfiguration != nil {
392+
// Use UIView.effectiveRadius(corner:) API for iOS 26+
393+
// This properly queries the actual corner radius from UICornerConfiguration
394+
path = UIBezierPath.path(roundedRect: containerView.frame, view: containerView)
395+
} else {
396+
path = UIBezierPath.path(roundedRect: containerView.frame,
397+
appearance: appearance)
398+
}
399+
#else
400+
path = UIBezierPath.path(roundedRect: containerView.frame,
401+
appearance: appearance)
402+
#endif
403+
374404
let size = window?.bounds.size ?? CGSize(width: 1000.0, height: 1000.0)
375405
path.append(UIBezierPath(rect: layer.bounds.insetBy(dx: -size.width,
376406
dy: -size.height)))
377407
mask.fillRule = .evenOdd
378408
mask.path = path.cgPath
409+
410+
#if compiler(>=6.2)
411+
if #available(iOS 26.0, *), appearance.cornerConfiguration != nil {
412+
// Corner curve is handled by UICornerConfiguration
413+
} else if #available(iOS 13.0, *) {
414+
containerView.layer.cornerCurve = appearance.cornerCurve
415+
mask.cornerCurve = appearance.cornerCurve
416+
}
417+
#else
379418
if #available(iOS 13.0, *) {
380419
containerView.layer.cornerCurve = appearance.cornerCurve
381420
mask.cornerCurve = appearance.cornerCurve
382421
}
422+
#endif
423+
383424
shadowLayer.mask = mask
384425
}
385426
CATransaction.commit()

0 commit comments

Comments
 (0)