Если мы находимся в реальном обработчике pre-commit, мы фактически не можем добавлять какие-либо новые заборы из-за ограничения CA - может это что-то значит здесь?

Я пытаюсь найти, почему коллекция пользовательских UIButtons не работает. В версии описанной в моем предыдущем сообщении, я создал круг UIButtons programmatically в Swift 3 и привязали круг к центру экрана. В этой версии был использован подкласс UIView - на основе учебника Apple Swift (реализация действия кнопки) - вместе с реализацией автозапуска, которая опирается на Imanou Petits отлично примеры кода (No.6 Anchor). В этой версии мне удалось заставить мои кнопки успешно вращаться, когда iPhone вращается, но действие-действие кнопки не работает.

Итак, теперь я попробовал альтернативную версию с помощью viewcontroller вместо подкласса UIView. На этот раз работает одна и та же кнопка-цель, но поворот телефона приводит к смещению изображения от центра, как показано ниже.

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

При каждом повороте в области отладки Xcode дважды появляется следующее сообщение.

    ***[App] if we're in the real pre-commit handler we can't 
    actually add any new fences due to CA restriction***

Сообщение происходит три раза из четырех, т.е. нет сообщения, когда телефон перевернут вверх дном. Это происходит, когда я запускаю код в своем предыдущем сообщении или код, показанный ниже. И в каждом случае не имело значения, было ли проверено или не проверено поле Upside Down.

введите описание изображения здесь Я также попробовал отключить OS_ACTIVITY MODE, но ничего не изменил, кроме как скрыть сообщение, которое потенциально может объяснить проблему. Кто-то более опытный, чем я, мы надеемся, узнаем, что это сообщение отладки означает либо в контексте моего предыдущего кода (показано здесь), либо мой последний код, показанный ниже.

ОРИГИНАЛЬНЫЙ КОД

import UIKit

class ViewController: UIViewController {

// MARK: Initialization

let points: Int             = 10    // 80 25 16 10  5
let dotSize: CGFloat        = 60    // 12 35 50 60 99
let radius: CGFloat         = 48    // 72 70 64 48 42
var centre: CGPoint?

var arcPoint                = CGFloat(M_PI * -0.5)  // clockwise from 12+ (not 3+)!

override func viewDidLoad() {
    super.viewDidLoad()

    let myView = UIView()
    myView.translatesAutoresizingMaskIntoConstraints   = false
    view.addSubview(myView)
    centre = centrePoint()
    let horizontalConstraint = myView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
    let verticalConstraint = myView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
    NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint])

    drawUberCircle()
    drawBoundaryCircles()

}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

func drawUberCircle() {

    // Create a CAShapeLayer

    let shapeLayer = CAShapeLayer()

    // give Bezier path layer properties
    shapeLayer.path = createBezierPath().cgPath

    // apply layer properties
    shapeLayer.strokeColor      = UIColor.cyan.cgColor
    shapeLayer.fillColor        = UIColor.cyan.cgColor
    shapeLayer.lineWidth        = 1.0

    // add layer
    view.layer.addSublayer(shapeLayer)
}

func createBezierPath() -> UIBezierPath {
    // create a new path
    let path  = UIBezierPath(arcCenter: centre!,
                             radius: radius * 2.0,
                             startAngle: CGFloat(M_PI * -0.5),
                             endAngle: CGFloat(M_PI * 1.5),
                             clockwise: true)
    return path
}

func drawBoundaryCircles() {

    for index in 1...points {
        let point: CGPoint  = makeBoundaryPoint(centre: centre!)
        drawButton(point: point, index: index)
    }
}

func makeBoundaryPoint(centre: CGPoint) -> (CGPoint) {
    arcPoint += arcAngle()
    print(arcPoint)
    let point   = CGPoint(x: centre.x + (radius * 2 * cos(arcPoint)), y: centre.y + (radius * 2 * sin(arcPoint)))
    return (point)
}

func arcAngle() -> CGFloat {
    return CGFloat(2.0 * M_PI) / CGFloat(points)
}


func centrePoint() -> CGPoint {
    return CGPoint(x: view.bounds.midX, y: view.bounds.midY)
}

func drawButton(point: CGPoint, index: Int) {
    let myButton = UIButton(type: .custom) as UIButton
    myButton.frame              = CGRect(x: point.x - (dotSize/2), y: point.y - (dotSize/2), width: dotSize, height: dotSize)
    myButton.backgroundColor    = UIColor.white
    myButton.layer.cornerRadius = dotSize / 2
    myButton.layer.borderWidth  = 1
    myButton.layer.borderColor  = UIColor.black.cgColor
    myButton.clipsToBounds      = true
    myButton.titleLabel!.font   =  UIFont(name: "HelveticaNeue-Thin", size: dotSize/2)
    myButton.setTitleColor(UIColor.red, for: .normal)
    myButton.setTitle(String(index), for: .normal)
    myButton.tag                = index;
    myButton.sendActions(for: .touchUpInside)
    myButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
    view.addSubview(myButton)
}

func buttonAction(myButton: UIButton) {
    let sender:UIButton = myButton
    print("Button \(sender.tag) works")
    }
}

Я все еще участвую в процессе обучения Swift, поэтому на данном этапе не имеет значения, использует ли решение viewcontroller или подкласс UIView, пока я могу организовать круг UIButtons, который все еще работает после того, как я настроил их с помощью autolayout. Каждое предложение приветствуется. Спасибо.

Решение

Сообщение, появившееся в области отладки Xcodes, и которое я использовал в строке темы этого сообщения, явно не было проблемой. Благодаря Rob Mayoff NSLayoutConstraint теперь вычисляет размеры и положение каждой кнопки, тогда как они были вычислены до времени выполнения в моем исходном коде. Его решение наряду с несколькими другими улучшениями теперь отражено в коде ниже. К этому я добавил исходную целевую кнопку для кнопок. Они не только работают, но и остаются заблокированными в центре просмотра всякий раз, когда меняется ориентация устройства.

Код может быть легко выполнен для работы с другой конфигурацией размера, изменяя значения радиуса, buttonCount и buttonSideLength (см. таблицу).

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

Вот код

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    createUberCircle()
    createButtons()
    }

override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .all }

private let radius: CGFloat = 85
private let buttonCount = 5
private let buttonSideLength: CGFloat = 100

private func createUberCircle() {
    let circle = ShapeView()
    circle.translatesAutoresizingMaskIntoConstraints = false
    circle.shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: -radius, y: -radius, width: 2*radius, height: 2*radius)).cgPath
    if buttonCount < 10 {
        circle.shapeLayer.fillColor = UIColor.clear.cgColor
    } else {
        circle.shapeLayer.fillColor = UIColor.cyan.cgColor
        }
    view.addSubview(circle)
    circle.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    circle.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }

private func createButtons() {
    for i in 1 ... buttonCount {
        createButton(number: i)
        }
    }

private func createButton(number: Int) {
    let button = UIButton(type: .custom)
    button.translatesAutoresizingMaskIntoConstraints = false
    button.backgroundColor = .white
    button.layer.cornerRadius = buttonSideLength / 2
    button.layer.borderWidth = 1
    button.layer.borderColor = UIColor.black.cgColor
    button.clipsToBounds = true
    button.titleLabel!.font = UIFont.systemFont(ofSize: buttonSideLength / 2)
    if buttonCount > 25 {
        button.setTitleColor(.clear, for: .normal)
    } else {
        button.setTitleColor(.red, for: .normal)
        }
    button.setTitle(String(number), for: .normal)
    button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
    button.tag = number

    view.addSubview(button)

    let radians = 2 * CGFloat.pi * CGFloat(number) / CGFloat(buttonCount) - CGFloat.pi / 2
    let xOffset = radius * cos(radians)
    let yOffset = radius * sin(radians)
    NSLayoutConstraint.activate([
        button.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: xOffset),
        button.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yOffset),
        button.widthAnchor.constraint(equalToConstant: buttonSideLength),
        button.heightAnchor.constraint(equalToConstant: buttonSideLength)
        ])
        }

func buttonAction(myButton: UIButton) {
    let sender:UIButton = myButton
    print("Button \(sender.tag) works")
        }

    }


class ShapeView: UIView {
    override class var layerClass: Swift.AnyClass { return CAShapeLayer.self }
    lazy var shapeLayer: CAShapeLayer = { self.layer as! CAShapeLayer }()
    }

Ответы

Ответ 1

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

Есть несколько проблем с кодом, который вы опубликовали:

  • Вы создаете myView и ограничиваете его центр, но вы не даете ему никаких ограничений по размеру. Кроме того, myView является локальной переменной, и вы не добавляете никаких подзонов в myView. Таким образом, myView является невидимым, безболезненным представлением без содержимого. Почему вы его вообще создаете?

  • Вы рисуете свой "uberCircle", используя слой с голой фигурой. "Голый", я имею в виду, что нет представления, чье свойство layer является этим слоем. Голые слои не участвуют в автозапуске.

  • Вы вычисляете положение каждой кнопки в зависимости от центра границ верхнего уровня. Вы делаете это во время viewDidLoad, но viewDidLoad вызывается до того, как вид верхнего уровня был изменен, чтобы соответствовать текущему устройству. Таким образом, ваше колесо даже не будет сосредоточено при запуске на некоторых устройствах.

  • Вы не устанавливаете никаких ограничений на кнопки или не устанавливаете свои маски авторезистировки. В результате, когда устройство вращается, вид верхнего уровня изменяется, но положение каждой кнопки (относительно верхнего левого угла верхнего уровня) остается прежним.

  • Включение флажка "Upside Down" недостаточно, чтобы разрешить перевернутую ориентацию на iPhone, только на iPads.

Вот изменения, которые вам нужно сделать:

  • Используйте вид, чтобы нарисовать "uberCircle". Если вы хотите использовать слой формы, создайте подкласс UIView, который использует CAShapeLayer для своего слоя. Вы можете скопировать класс ShapeView из этого ответа.

  • Задайте ограничения из центра uberCircle в центр верхнего уровня, чтобы поддерживать uberCircle в центре, когда вид верхнего уровня меняет размер.

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

  • Переопределить supportedInterfaceOrientations, чтобы включить перевернутую ориентацию (в дополнение к проверке флажка "вверх дном" ).

  • Избавиться от myView в viewDidLoad. Вам это не нужно.

  • Избавьтесь от свойства centre. Вам это не нужно.

Таким образом:

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        createUberCircle()
        createButtons()
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return .all }

    private let radius: CGFloat = 96
    private let buttonCount = 10
    private let buttonSideLength: CGFloat = 60

    private func createUberCircle() {
        let circle = ShapeView()
        circle.translatesAutoresizingMaskIntoConstraints = false
        circle.shapeLayer.path = UIBezierPath(ovalIn: CGRect(x: -radius, y: -radius, width: 2*radius, height: 2*radius)).cgPath
        circle.shapeLayer.fillColor = UIColor.cyan.cgColor
        view.addSubview(circle)
        circle.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        circle.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
    }

    private func createButtons() {
        for i in 1 ... buttonCount {
            createButton(number: i)
        }
    }

    private func createButton(number: Int) {
        let button = UIButton(type: .custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = .white
        button.layer.cornerRadius = buttonSideLength / 2
        button.layer.borderWidth = 1
        button.layer.borderColor = UIColor.black.cgColor
        button.clipsToBounds = true
        button.titleLabel!.font = UIFont.systemFont(ofSize: buttonSideLength / 2)
        button.setTitleColor(.red, for: .normal)
        button.setTitle(String(number), for: .normal)
        view.addSubview(button)

        let radians = 2 * CGFloat.pi * CGFloat(number) / CGFloat(buttonCount) - CGFloat.pi / 2
        let xOffset = radius * cos(radians)
        let yOffset = radius * sin(radians)
        NSLayoutConstraint.activate([
            button.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: xOffset),
            button.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: yOffset),
            button.widthAnchor.constraint(equalToConstant: buttonSideLength),
            button.heightAnchor.constraint(equalToConstant: buttonSideLength)
            ])
    }

}

class ShapeView: UIView {

    override class var layerClass: Swift.AnyClass { return CAShapeLayer.self }

    lazy var shapeLayer: CAShapeLayer = { self.layer as! CAShapeLayer }()

}

Ответ 2

Я бы просто ограничил родительский UIView и сдерживал все кнопки в центре родительского представления и переопределял layoutSubviews. В представлении макета вы можете манипулировать константами ограничений центра X и centerY, чтобы кнопки могли их переместить. В качестве альтернативы вы можете просто сосредоточить все их и использовать свойство .transform каждой кнопки, чтобы переместить их на место.