Почему UIView не остается в центре, пока он вращается?

Я пытаюсь откалибровать UIView, чтобы оставаться в центре, когда он вращается. Это следует за проблемой, идентифицированной наилучшими ответами на мой предыдущий вопрос. С тех пор я разработал код на основе одного ответа, предлагающего использовать viewWillAppear или viewDidLayoutSubviews вместо viewDidLoad.

Поскольку я новичок в Swift, мне пришлось вернуться к учебнику Swift. Внедрение пользовательского элемента управления, описывающего как сделать subClass из UIView. Мой код может рисовать круг, используя любую из трех функций: viewWillAppear, viewDidLayoutSubviews или viewDidLoad.

Однако, когда я визуализую код, круг не всегда остается в центре, когда экран вращается. Ниже приведены последовательные вращения, которые происходят с использованием viewDidLoad или viewWillAppear viewDidLoad или viewWillAppear Когда приложение запускается на экране в портретной ориентации, проблема появляется сразу после первого вращения независимо от того, как левая или правая. Если запустить в альбомной ориентации, можно пропустить проблему, если вы не пробовали хотя бы четыре последовательных вращения.

Ниже приведены последовательные вращения, которые происходят с использованием viewDidLayoutSubviews, где поворот иногда приводит к тому, что внутрицентровая окружность перекрывает нецентральный круг. viewDidLayoutSubviews Это может произойти после нескольких поворотов, но нужно выполнить не менее четырех оборотов в чтобы заметить проблему.

Вот код для CircleView - моя попытка сделать подкласс UIView

import UIKit

class CircleView: UIView {
    let radius          = CGFloat(200)
    let x: CGFloat      = 0
    let y: CGFloat      = 0

// MARK: Initialization

required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

    override init(frame: CGRect) {
    super.init(frame: frame)
    setup()
}

func setup() {

    // Create a CAShapeLayer
    let shapeLayer = CAShapeLayer()

    // The Bezier path that we made needs to be converted
    // to a CGPath before it can be used on a layer.

    shapeLayer.path = createBezierPath().cgPath

    // apply other properties related to the path

    shapeLayer.strokeColor      = UIColor.red.cgColor
    shapeLayer.fillColor        = UIColor.white.cgColor
    shapeLayer.lineWidth        = 8.0

    // add the new layer to our custom view
    self.layer.addSublayer(shapeLayer)
}

func createBezierPath() -> UIBezierPath {
    // create a new path

    let path  = UIBezierPath(arcCenter: CGPoint(x: x, y: y),
                radius: radius,
                startAngle: CGFloat(M_PI * -0.5),
                endAngle:CGFloat(M_PI * 1.5),
                clockwise: true)

    path.fill()
    path.stroke()
    return path
    } 
}

и вот код для ViewController

import UIKit

class ViewController: UIViewController {
    var circle: CircleView?
    var x: CGFloat?
    var y: CGFloat?

    override func viewDidLoad() {
    super.viewDidLoad()
    let centre = centreCircle()

    // create a new UIView and add it to the view controller
    let circleView = CircleView()
    circleView.frame = CGRect(x: centre.x, y: centre.y, width: 0, height: 0)
    view.addSubview(circleView)
    }

    func centreCircle() -> CGPoint {
    circle?.bounds = UIScreen.main.bounds
    return CGPoint(x: UIScreen.main.bounds.midX, y: UIScreen.main.bounds.midY)
    }
}

И вот код для расширения UIScreen, который возвращает центральную точку экрана

import UIKit

extension UIScreen {
    var centre: CGPoint {
    return CGPoint(x: bounds.midX, y: bounds.midY)
    }
}

Чтобы произвести приведенные выше вращения, я заменил функцию viewDidLoad на viewWillAppear

    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    let centre = centreCircle()

    // create a new UIView and add it to the view controller
    let circleView = CircleView()
    circleView.frame = CGRect(x: centre.x, y: centre.y, width: 0, height: 0)
    view.addSubview(circleView)
    }

или viewDidLayoutSubviews

    override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let centre = centreCircle()

    // create a new UIView and add it to the view controller
    let circleView = CircleView()
    circleView.frame = CGRect(x: centre.x, y: centre.y, width: 0, height: 0)
    view.addSubview(circleView)
    }

Может ли кто-нибудь увидеть проблему в моем коде, которая может остановить вращение от работы?

Ответ 1

Прежде всего, представление не выставляется правильно в viewdidload и viewwillappear, и вы явно определяете фрейм со значениями, что я считаю крайне расстраивающим, как новый программист. Это все еще раздражает меня, особенно когда речь заходит об изменении размеров меток. Таким образом, размещение вашего представления и основного экрана centreX и centreY означает, что ваш взгляд будет придерживаться в тот момент, когда вы вращаетесь.

Исправить это, изучив автозапуск. Сначала это отстой, но в конечном итоге это облегчит вашу жизнь. Взгляните на метод в этом расширении. Я использую его, когда хочу сосредоточить взгляд на другом представлении

extension UIView {


    func constrainAndCenter(in view: UIView, withRelativeHeight: CGFloat, withRelativeWidth: CGFloat) -> [NSLayoutConstraint] {
        self.translatesAutoresizingMaskIntoConstraints = false

        let x = NSLayoutConstraint(item: self, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0)
        let w = NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: withRelativeWidth, constant: 0)
        let h = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: withRelativeHeight, constant: 0)
        let y = NSLayoutConstraint(item: self, attribute: .centerY, relatedBy: .equal, toItem: view, attribute: .centerY, multiplier: 1, constant: 0)

        return ([x,w,h,y])
    }
}

// then in viewdidload or whereever
let circleView = CircleView() 
let circleViewConstraints = circleView.constrainAndCenter(in: view,....)
view.addSubview(circleView)
NSLayoutConstraint.activate(circleViewConstraints)

Это будет привязать его к середине. Вы также можете сделать это в построителе интерфейса.

Ответ 2

вы должны заменить frame.bounds на frame.center, чтобы центр был рассчитан правильно.