Получение автоматического размещения для работы с подклассом UIControl - PullRequest
0 голосов
/ 02 января 2019

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

Элемент управления выкладывается, как и ожидалось, когда я устанавливаю кадр.

enter image description here

circleContol = CircleControl(size: 80, title: "My Button", circleStrokeSize: 4, image: testImage, imageColour: .yellow, circleColour: .red, textColour: .red)
circleContol.center = CGPoint(x: 150, y: 150)
view.addSubview(circleContol)

Тем не менее, я изо всех сил пытаюсь получить контроль, чтобы хорошо играть с Autolayout.

то есть используя что-то вроде

NSLayoutConstraint.activate([
    circleContol.centerYAnchor.constraint(equalTo: view.centerYAnchor),
    circleContol.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])

приводит к

enter image description here

Я пытался настроить автопоставку в моем подклассе UIControl, но не смог получитьэто правильно.Например, я попробовал следующее, но я не уверен, является ли это правильным подходом или лучшим местом для установки этих ограничений - особенно с кругом shapeLayer, который, кажется, является совершенно другой проблемой.

override func updateConstraints() {

    NSLayoutConstraint.activate([
        imageOverlay.centerYAnchor.constraint(equalTo: centerYAnchor),
        imageOverlay.centerXAnchor.constraint(equalTo: centerXAnchor),
        imageOverlay.heightAnchor.constraint(equalToConstant: radius),
        imageOverlay.widthAnchor.constraint(equalToConstant: radius),
    ])

    super.updateConstraints()
}

Вот код liveView игровой площадки, надеюсь, кто-то может дать некоторые рекомендации.Много кода, но я чувствовал, что это лучший способ увидеть проблемы

Спасибо

Каждый приведенный выше пример можно увидеть с помощью этих методов в playGround

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // 1. Lays out as expected
    createControl()

    // 2. Views all out of place
    // createUsingAutoLayout()
}

Код PlayGround

import UIKit
import PlaygroundSupport

class CircleControl: UIControl {

    private var shaperLayer: CAShapeLayer!
    private var stroke: CGFloat!
    private var padding: CGFloat!
    private var circleColour: UIColor!
    private var title: String!
    private var imageColour: UIColor!
    private var textColour: UIColor!
    private var imageOverlay: UIImageView!
    private let fontSize: CGFloat = 20

    private var actualRadius: CGFloat {
        let halfSquare: CGFloat = max(bounds.size.width, bounds.size.height) / 2
        let temp: CGFloat = halfSquare * halfSquare + halfSquare * halfSquare
        let radius: CGFloat = temp.squareRoot()
        return radius
    }

    private var radius: CGFloat {
        return actualRadius + padding + (stroke * 0.5)
    }

    private var textRadius: CGFloat {
        return actualRadius + padding + stroke + (fontSize * 0.5)
    }

    init(size: Int, title: String, circleStrokeSize: CGFloat, circlePadding: CGFloat = 0, image: UIImage, imageColour: UIColor, circleColour: UIColor, textColour: UIColor) {
        let f = CGRect(x: 0, y: 0, width: size, height: size)
        self.stroke = circleStrokeSize
        self.padding = circlePadding
        self.circleColour = circleColour
        self.imageColour = imageColour
        self.textColour = textColour
        self.title = title
        super.init(frame: f)

        // add subviews
        updateView()

        // add image to button
        let tinted = image.withRenderingMode(.alwaysTemplate)
        imageOverlay.image = tinted
    }

    private func addTextToView() {
        let label = CurvedTextView(frame: CGRect(x: 0, y: 0, width: 300, height: 300), title: title, r: textRadius, fontSize: fontSize, fontColour: textColour, location: .bottom)
        label.center = imageOverlay.center
        label.isUserInteractionEnabled = false
        label.backgroundColor = UIColor.clear
        label.translatesAutoresizingMaskIntoConstraints = false
        addSubview(label)
    }

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

    func updateView() {
        addImageOverlay()
        addCircle()
        addTextToView()
    }

    func addImageOverlay() {
        imageOverlay = UIImageView(frame: bounds)
        imageOverlay.tintColor = imageColour
        imageOverlay.contentMode = .scaleAspectFill
        addSubview(imageOverlay)
        imageOverlay.isUserInteractionEnabled = false
        imageOverlay.translatesAutoresizingMaskIntoConstraints = false
    }

    func addCircle() {
        if let s = shaperLayer {
            s.removeFromSuperlayer()
        }

        shaperLayer = CAShapeLayer()
        let circularPath = UIBezierPath(arcCenter: UIView(frame: bounds).center, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true)
        shaperLayer.path = circularPath.cgPath
        shaperLayer.strokeColor = circleColour.cgColor
        shaperLayer.fillColor = UIColor.clear.cgColor
        shaperLayer.lineWidth = stroke
        layer.addSublayer(shaperLayer)
    }
}

class CurvedTextView: UIView {

    var radius: CGFloat!
    var fontSize: CGFloat!
    var fontColour: UIColor!
    var labelPost: LabelPostition = .bottom
    var title: String!

    init(frame: CGRect, title: String, r: CGFloat, fontSize: CGFloat, fontColour: UIColor, location: LabelPostition) {
        self.radius = r
        self.fontSize = fontSize
        self.fontColour = fontColour
        self.labelPost = location
        self.title = title
        super.init(frame: frame)
    }

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

    enum LabelPostition {
        case bottom
    }

    func centreArcPerpendicular(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, clockwise: Bool){
        let characters: [String] = str.map { String($0) }
        let l = characters.count
        let attributes: [NSAttributedString.Key: Any] = [NSAttributedString.Key.font: font, NSAttributedString.Key.kern: 0.8]

        var arcs: [CGFloat] = []
        var totalArc: CGFloat = 0

        for i in 0 ..< l {
            arcs += [chordToArc(characters[i].size(withAttributes: attributes).width, radius: r)]
            totalArc += arcs[i]
        }

        let direction: CGFloat = clockwise ? -1 : 1
        let slantCorrection: CGFloat = clockwise ? -.pi / 2 : .pi / 2

        var thetaI = theta - direction * totalArc / 2

        for i in 0 ..< l {
            thetaI += direction * arcs[i] / 2
            centre(text: characters[i], context: context, radius: r, angle: thetaI, colour: c, font: font, slantAngle: thetaI + slantCorrection)

            thetaI += direction * arcs[i] / 2
        }
    }

    func centre(text str: String, context: CGContext, radius r: CGFloat, angle theta: CGFloat, colour c: UIColor, font: UIFont, slantAngle: CGFloat) {
        let attributes = [NSAttributedString.Key.foregroundColor: c, NSAttributedString.Key.font: font]
        context.saveGState()
        context.scaleBy(x: 1, y: -1)
        context.translateBy(x: r * cos(theta), y: -(r * sin(theta)))
        context.rotate(by: -slantAngle)
        let offset = str.size(withAttributes: attributes)
        context.translateBy (x: -offset.width / 2, y: -offset.height / 2)
        str.draw(at: CGPoint(x: 0, y: 0), withAttributes: attributes)
        context.restoreGState()
    }

    func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
        return 2 * asin(chord / (2 * radius))
    }

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }

        let size = self.bounds.size

        context.translateBy (x: size.width / 2, y: size.height / 2)
        context.scaleBy (x: 1, y: -1)

        let f = UIFont.systemFont(ofSize: fontSize, weight: UIFont.Weight.semibold)

        var startAngle: CGFloat = .pi
        var clock = true

        switch labelPost {
        case .bottom:
            startAngle = -(.pi / 2)
            clock = false
        }

        centreArcPerpendicular(text: title, context: context, radius: radius, angle: startAngle, colour: fontColour, font: f, clockwise: clock)
    }
}

class MyViewController : UIViewController {

    var circleContol: CircleControl!
    private let testImage = UIImage(named: "faceLike.png")!

    override func loadView() {
        let view = UIView()
        view.backgroundColor = .lightGray
        self.view = view
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 1. Lays out as expected
        createControl()

        // 2. Views all out of place
        // createUsingAutoLayout()
    }

    func createControl() {
        circleContol = CircleControl(size: 80, title: "My Button", circleStrokeSize: 4, image: testImage, imageColour: .yellow, circleColour: .red, textColour: .red)
        circleContol.center = CGPoint(x: 150, y: 150)
        view.addSubview(circleContol)
    }

    func createUsingAutoLayout() {
        circleContol = CircleControl(size: 150, title: "My Button", circleStrokeSize: 4, image: testImage, imageColour: .yellow, circleColour: .red, textColour: .red)
        view.addSubview(circleContol)

        circleContol.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            circleContol.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            circleContol.centerXAnchor.constraint(equalTo: view.centerXAnchor)
        ])
    }
}

PlaygroundPage.current.liveView = MyViewController()

1 Ответ

0 голосов
/ 02 января 2019

Я не могу управлять твоей детской площадкой, но у меня могут быть некоторые идеи.

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

И вы не должны устанавливать свои ограничения в методе updateConstraints, ограничения должны быть установлены только один раз, чтобы autoLayout работал.

Попробуйте отредактировать:

private func addTextToView() {
    let label = CurvedTextView(frame: CGRect(x: 0, y: 0, width: 300, height: 300), title: title, r: textRadius, fontSize: fontSize, fontColour: textColour, location: .bottom)
    label.isUserInteractionEnabled = false
    label.backgroundColor = UIColor.clear
    label.translatesAutoresizingMaskIntoConstraints = false

    addSubview(label)

    NSLayoutConstraint.activate([
        label.topAnchor.constraint(equalTo: topAnchor),
        label.bottomAnchor.constraint(equalTo: bottomAnchor),
        label.trailingAnchor.constraint(equalTo: trailingAnchor),
        label.leadingAnchor.constraint(equalTo: leadingAnchor)
        ])
}

func addImageOverlay() {
    imageOverlay = UIImageView(frame: bounds)
    imageOverlay.tintColor = imageColour
    imageOverlay.contentMode = .scaleAspectFill
    addSubview(imageOverlay)
    imageOverlay.isUserInteractionEnabled = false
    imageOverlay.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        imageOverlay.topAnchor.constraint(equalTo: topAnchor),
        imageOverlay.bottomAnchor.constraint(equalTo: bottomAnchor),
        imageOverlay.trailingAnchor.constraint(equalTo: trailingAnchor),
        imageOverlay.leadingAnchor.constraint(equalTo: leadingAnchor)
    ])
}

Надеюсьэто помогает


В конце концов, решение было тривиальным

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

func addImageOverlay() {
    imageOverlay = UIImageView(frame: bounds)
    imageOverlay.tintColor = imageColour
    imageOverlay.contentMode = .scaleAspectFill
    addSubview(imageOverlay)
    imageOverlay.isUserInteractionEnabled = false
    imageOverlay.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint.activate([
        imageOverlay.topAnchor.constraint(equalTo: topAnchor),
        imageOverlay.bottomAnchor.constraint(equalTo: bottomAnchor),
        imageOverlay.trailingAnchor.constraint(equalTo: trailingAnchor),
        imageOverlay.leadingAnchor.constraint(equalTo: leadingAnchor),
        imageOverlay.heightAnchor.constraint(equalToConstant: bounds.size.width),
        imageOverlay.widthAnchor.constraint(equalToConstant: bounds.size.width)
    ])
}
...