Как проследить границы экрана iPhone (включая выемку для серий X, 11 или 12)?

#swift #xcode #uibezierpath #cashapelayer

#swift #xcode #uibezierpath #cashapelayer

Вопрос:

Я пытаюсь провести линию по внешней стороне основного вида — трассировка вдоль края экрана. На следующем рисунке показан контур, а приведенный ниже код показывает, как он анимируется.

Граница iPhone

Проблема в том, что анимация сначала рисует внешний край, а затем область вокруг выемки.

Мне нужно, чтобы он начал рисовать внешний край, опустился вниз и обводил выемку, а затем продолжал вдоль внешнего края, пока он не закончится.

 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 файл нужно будет удалить, Application Scene Manifest запись в Info.plist нем нужно будет удалить, и var window: UIWindow? ее нужно будет добавить AppDelegate.swift .

Ответ №1:

У вас возникла эта проблема, потому что вы создаете две отдельные фигуры и добавляете одну к другой. Итак, ваш путь правильно прорисован.

Вам нужно создать одну фигуру, используя точные координаты, чтобы она была правильно нарисована.
вот простая фигура без каких-либо закругленных углов:

 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)
 

введите описание изображения здесь