您如何追踪 iPhone 屏幕的边框(包括 X、11 或 12 系列的缺口)?
How do you trace the border of an iPhone screen (including the notch for an X, 11, or 12 series)?
我正在尝试在主视图的外部描绘一条线 - 沿着屏幕的边缘描绘。下图显示了轮廓,下面的代码显示了它是如何动画的。
问题是,动画先绘制外边缘,然后绘制凹口周围的区域。
我需要它开始绘制外边缘,下降并围绕凹口绘制,然后继续沿着外边缘绘制直到完成。
import UIKit
class ViewController: UIViewController {
var layer: CAShapeLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
setupBorder()
animateBorder()
}
func setupBorder() {
let bounds = self.view.bounds
if UIDevice.current.hasNotch {
// FIXME: needs tweaks for:
// 12 pro max (corners and notch)
// 12 pro (corners)
// 12 mini (notch)
// the math works for all X and 11 series
// border around the phone screen
let framePath = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 40, height: 40))
// Math courtesy of:
// https://www.paintcodeapp.com/news/iphone-x-screen-demystified
let devicePointWidth = UIScreen.main.bounds.size.width
let w = devicePointWidth * 83 / 375
let n = devicePointWidth * 209 / 375
let notchBounds = CGRect(x: w, y: -10, width: n, height: 40)
let notchPath = UIBezierPath(roundedRect: notchBounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 20, height: 20))
// This is the problem. The framePath is drawn first,
// and then the notchPath is drawn. I need these to be
// mathematically merged
framePath.append(notchPath)
framePath.usesEvenOddFillRule = true
layer.path = framePath.cgPath
} else {
// if device is an 8 or lower, the border of the screen
// is a rectangle
layer.path = UIBezierPath(rect: bounds).cgPath
}
layer.strokeColor = UIColor.blue.cgColor
layer.strokeEnd = 0.0
layer.lineWidth = 20.0
layer.fillColor = nil
self.view.layer.addSublayer(layer)
}
func animateBorder() {
CATransaction.begin()
let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.strokeEnd))
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = 6
CATransaction.setCompletionBlock { [weak self] in
self?.layer.strokeColor = UIColor.cyan.cgColor
self?.layer.strokeEnd = 1.0
}
layer.add(animation, forKey: "stroke-screen")
CATransaction.commit()
}
}
extension UIDevice {
var hasNotch: Bool {
// FIXME: Does not work with apps that use SceneDelegate
// Requires the window var in the AppDelegate
if #available(iOS 11.0, *) {
return UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0 > 20
}
return false
}
}
上面的代码可以在新项目中使用,但是SceneDelegate.swift
文件需要删除,Info.plist
中的Application Scene Manifest
条目需要删除, var window: UIWindow?
需要添加到 AppDelegate.swift
。
您遇到这个问题是因为您正在创建两个单独的形状并将一个附加到下一个。所以你的路径被正确绘制了。
您需要使用精确坐标创建一个形状,以便正确绘制。
这是一个没有任何圆角的简单形状:
let framePath = UIBezierPath()
framePath.move(to: .zero)
framePath.addLine(to: CGPoint(x: w, y: 0))
framePath.addLine(to: CGPoint(x: w, y: 30))
framePath.addLine(to: CGPoint(x: w+n, y: 30))
framePath.addLine(to: CGPoint(x: w+n, y: 0))
framePath.addLine(to: CGPoint(x: devicePointWidth, y: 0))
framePath.addLine(to: CGPoint(x: devicePointWidth, y: self.view.bounds.height))
framePath.addLine(to: CGPoint(x: 0, y: self.view.bounds.height))
framePath.addLine(to: .zero)
然后你可以使用addArc(withCenter:radius:startAngle:endAngle:clockwise:)
添加弯曲的部分。
这是使用您计算出的一些值的粗略草稿:
let circleTop = CGFloat(3*Double.pi / 2)
let circleRight = CGFloat(0)
let circleBottom = CGFloat(Double.pi / 2)
let circleLeft = CGFloat(Double.pi)
let framePath = UIBezierPath()
framePath.move(to: CGPoint(x: 40, y: 0))
framePath.addLine(to: CGPoint(x: w-6, y: 0))
framePath.addArc(withCenter: CGPoint(x: w-6, y: 6), radius: 6, startAngle: circleTop, endAngle: circleRight, clockwise: true)
framePath.addArc(withCenter: CGPoint(x: w+20, y: 10), radius: 20, startAngle: circleLeft, endAngle: circleBottom, clockwise: false)
framePath.addLine(to: CGPoint(x: w+n-20, y: 30))
framePath.addArc(withCenter: CGPoint(x: w+n-20, y: 10), radius: 20, startAngle: circleBottom, endAngle: circleRight, clockwise: false)
framePath.addArc(withCenter: CGPoint(x: w+n+6, y: 6), radius: 6, startAngle: CGFloat(Double.pi), endAngle: circleTop, clockwise: true)
framePath.addLine(to: CGPoint(x: devicePointWidth-40, y: 0))
framePath.addArc(withCenter: CGPoint(x: devicePointWidth-40, y: 40), radius: 40, startAngle: circleTop, endAngle: circleRight, clockwise: true)
framePath.addLine(to: CGPoint(x: devicePointWidth, y: self.view.bounds.height - 40))
framePath.addArc(withCenter: CGPoint(x: UIScreen.main.bounds.size.width-40, y: UIScreen.main.bounds.size.height-40), radius: 40, startAngle: circleRight, endAngle: circleBottom, clockwise: true)
framePath.addLine(to: CGPoint(x: 40, y: self.view.bounds.height))
framePath.addArc(withCenter: CGPoint(x: 40, y: UIScreen.main.bounds.size.height-40), radius: 40, startAngle: circleBottom, endAngle: circleLeft, clockwise: true)
framePath.addLine(to: CGPoint(x: 0, y: 40))
framePath.addArc(withCenter: CGPoint(x: 40, y: 40), radius: 40, startAngle: circleLeft, endAngle: circleTop, clockwise: true)
我正在尝试在主视图的外部描绘一条线 - 沿着屏幕的边缘描绘。下图显示了轮廓,下面的代码显示了它是如何动画的。
问题是,动画先绘制外边缘,然后绘制凹口周围的区域。
我需要它开始绘制外边缘,下降并围绕凹口绘制,然后继续沿着外边缘绘制直到完成。
import UIKit
class ViewController: UIViewController {
var layer: CAShapeLayer = CAShapeLayer()
override func viewDidLoad() {
super.viewDidLoad()
setupBorder()
animateBorder()
}
func setupBorder() {
let bounds = self.view.bounds
if UIDevice.current.hasNotch {
// FIXME: needs tweaks for:
// 12 pro max (corners and notch)
// 12 pro (corners)
// 12 mini (notch)
// the math works for all X and 11 series
// border around the phone screen
let framePath = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 40, height: 40))
// Math courtesy of:
// https://www.paintcodeapp.com/news/iphone-x-screen-demystified
let devicePointWidth = UIScreen.main.bounds.size.width
let w = devicePointWidth * 83 / 375
let n = devicePointWidth * 209 / 375
let notchBounds = CGRect(x: w, y: -10, width: n, height: 40)
let notchPath = UIBezierPath(roundedRect: notchBounds, byRoundingCorners: [.bottomLeft, .bottomRight], cornerRadii: CGSize(width: 20, height: 20))
// This is the problem. The framePath is drawn first,
// and then the notchPath is drawn. I need these to be
// mathematically merged
framePath.append(notchPath)
framePath.usesEvenOddFillRule = true
layer.path = framePath.cgPath
} else {
// if device is an 8 or lower, the border of the screen
// is a rectangle
layer.path = UIBezierPath(rect: bounds).cgPath
}
layer.strokeColor = UIColor.blue.cgColor
layer.strokeEnd = 0.0
layer.lineWidth = 20.0
layer.fillColor = nil
self.view.layer.addSublayer(layer)
}
func animateBorder() {
CATransaction.begin()
let animation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.strokeEnd))
animation.timingFunction = CAMediaTimingFunction(name: .linear)
animation.fromValue = 0.0
animation.toValue = 1.0
animation.duration = 6
CATransaction.setCompletionBlock { [weak self] in
self?.layer.strokeColor = UIColor.cyan.cgColor
self?.layer.strokeEnd = 1.0
}
layer.add(animation, forKey: "stroke-screen")
CATransaction.commit()
}
}
extension UIDevice {
var hasNotch: Bool {
// FIXME: Does not work with apps that use SceneDelegate
// Requires the window var in the AppDelegate
if #available(iOS 11.0, *) {
return UIApplication.shared.delegate?.window??.safeAreaInsets.bottom ?? 0 > 20
}
return false
}
}
上面的代码可以在新项目中使用,但是SceneDelegate.swift
文件需要删除,Info.plist
中的Application Scene Manifest
条目需要删除, var window: UIWindow?
需要添加到 AppDelegate.swift
。
您遇到这个问题是因为您正在创建两个单独的形状并将一个附加到下一个。所以你的路径被正确绘制了。
您需要使用精确坐标创建一个形状,以便正确绘制。
这是一个没有任何圆角的简单形状:
let framePath = UIBezierPath()
framePath.move(to: .zero)
framePath.addLine(to: CGPoint(x: w, y: 0))
framePath.addLine(to: CGPoint(x: w, y: 30))
framePath.addLine(to: CGPoint(x: w+n, y: 30))
framePath.addLine(to: CGPoint(x: w+n, y: 0))
framePath.addLine(to: CGPoint(x: devicePointWidth, y: 0))
framePath.addLine(to: CGPoint(x: devicePointWidth, y: self.view.bounds.height))
framePath.addLine(to: CGPoint(x: 0, y: self.view.bounds.height))
framePath.addLine(to: .zero)
然后你可以使用addArc(withCenter:radius:startAngle:endAngle:clockwise:)
添加弯曲的部分。
这是使用您计算出的一些值的粗略草稿:
let circleTop = CGFloat(3*Double.pi / 2)
let circleRight = CGFloat(0)
let circleBottom = CGFloat(Double.pi / 2)
let circleLeft = CGFloat(Double.pi)
let framePath = UIBezierPath()
framePath.move(to: CGPoint(x: 40, y: 0))
framePath.addLine(to: CGPoint(x: w-6, y: 0))
framePath.addArc(withCenter: CGPoint(x: w-6, y: 6), radius: 6, startAngle: circleTop, endAngle: circleRight, clockwise: true)
framePath.addArc(withCenter: CGPoint(x: w+20, y: 10), radius: 20, startAngle: circleLeft, endAngle: circleBottom, clockwise: false)
framePath.addLine(to: CGPoint(x: w+n-20, y: 30))
framePath.addArc(withCenter: CGPoint(x: w+n-20, y: 10), radius: 20, startAngle: circleBottom, endAngle: circleRight, clockwise: false)
framePath.addArc(withCenter: CGPoint(x: w+n+6, y: 6), radius: 6, startAngle: CGFloat(Double.pi), endAngle: circleTop, clockwise: true)
framePath.addLine(to: CGPoint(x: devicePointWidth-40, y: 0))
framePath.addArc(withCenter: CGPoint(x: devicePointWidth-40, y: 40), radius: 40, startAngle: circleTop, endAngle: circleRight, clockwise: true)
framePath.addLine(to: CGPoint(x: devicePointWidth, y: self.view.bounds.height - 40))
framePath.addArc(withCenter: CGPoint(x: UIScreen.main.bounds.size.width-40, y: UIScreen.main.bounds.size.height-40), radius: 40, startAngle: circleRight, endAngle: circleBottom, clockwise: true)
framePath.addLine(to: CGPoint(x: 40, y: self.view.bounds.height))
framePath.addArc(withCenter: CGPoint(x: 40, y: UIScreen.main.bounds.size.height-40), radius: 40, startAngle: circleBottom, endAngle: circleLeft, clockwise: true)
framePath.addLine(to: CGPoint(x: 0, y: 40))
framePath.addArc(withCenter: CGPoint(x: 40, y: 40), radius: 40, startAngle: circleLeft, endAngle: circleTop, clockwise: true)