Swift 如何追踪iPhone屏幕的边框(包括X、11或12系列的凹口)?

Swift 如何追踪iPhone屏幕的边框(包括X、11或12系列的凹口)?,swift,xcode,uibezierpath,cashapelayer,Swift,Xcode,Uibezierpath,Cashapelayer,我试图在主视图的外面画一条线——沿着屏幕的边缘画。下图显示了轮廓,下面的代码显示了它是如何设置动画的 问题是,动画首先绘制外边缘,然后绘制凹口周围的区域 我需要它开始画外缘,放下并围绕槽口画,然后沿着外缘继续画,直到画完为止 import UIKit class ViewController: UIViewController { var layer: CAShapeLayer = CAShapeLayer() override func viewDidLo

我试图在主视图的外面画一条线——沿着屏幕的边缘画。下图显示了轮廓,下面的代码显示了它是如何设置动画的

问题是,动画首先绘制外边缘,然后绘制凹口周围的区域

我需要它开始画外缘,放下并围绕槽口画,然后沿着外缘继续画,直到画完为止

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中的应用程序场景清单条目需要删除,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)
然后,您可以使用addArcwithCenter:radius:StartTangle:endAngle:顺时针:添加曲线零件

以下是使用您计算的一些值的粗略草稿:

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)

您之所以会遇到这个问题,是因为您正在创建两个单独的形状,并将一个附加到另一个。因此,您的路径正在正确绘制

您需要使用精确的坐标创建一个形状,以便正确绘制它。 这是一个没有任何圆角的简单形状:

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)
然后,您可以使用addArcwithCenter:radius:StartTangle:endAngle:顺时针:添加曲线零件

以下是使用您计算的一些值的粗略草稿:

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)