Мне удалось получить свой собственный элемент управления в основном так, как я этого хочу, но я бы хотел, чтобы он работал с autolayout.Я сделал много ошибок и ошибок, но не получил никуда.
Элемент управления выкладывается, как и ожидалось, когда я устанавливаю кадр.
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)
])
приводит к
Я пытался настроить автопоставку в моем подклассе 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()