Vertical UISlider constraints - swift

I am creating a vertical UISlider. I have created the view it is in using all code. (the rest of the storyboard elements are constrained using interface builder)
From what I have read, to create a vertical UISlider you give the UISlider a width and then rotate it. Since the height of the container that the UISlider sits in varies by screen size I do not want to give it a fixed height (width).
This is what I was thinking
// Mark: Slider View
let leftSlider = UISlider()
let centerSlider = UISlider()
let rightSlider = UISlider()
let colorSliders = [leftSlider, centerSlider, rightSlider]
for slider in colorSliders {
slider.translatesAutoresizingMaskIntoConstraints = false
sliderContainer.addSubview(slider)
let w = sliderContainer.bounds.width
slider.bounds.size.width = w
slider.center = CGPoint(x: w/2, y: w/2)
slider.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
slider.value = 0
slider.minimumValue = 0
slider.maximumValue = 255
let sliderTopConstraint = slider.topAnchor.constraint(equalTo: centerHiddenView.bottomAnchor, constant: 5)
let sliderBottomConstraint = slider.bottomAnchor.constraint(equalTo: sliderContainer.bottomAnchor, constant: 5)
NSLayoutConstraint.activate([sliderTopConstraint, sliderBottomConstraint])
slider.backgroundColor = .purple
slider.isEnabled = true
slider.isUserInteractionEnabled = true
}
let sliderContainerWidth: CGFloat = sliderContainer.frame.width
let centerSliderHorizontalConstraints = centerSlider.centerXAnchor.constraint(equalTo: sliderContainer.centerXAnchor)
let widthConstraint = centerSlider.widthAnchor.constraint(equalToConstant: sliderContainerWidth)
let centerSliderWidthConstraint = centerSlider.widthAnchor.constraint(equalToConstant: 90)
NSLayoutConstraint.activate([centerSliderHorizontalConstraints, centerSliderWidthConstraint, widthConstraint])
But when I run it I get this
which is far better than what I had earlier today. However, I would like the slider to be normal width..and well look normal just vertical
Any ideas on what I missed?
(Oh ignore that little purple offshoot to the left, that is the other 2 sliders that I added but didn't constrain yet.)

You're changing the bounds and the transform of your UISlider and are using Auto-Layout at the same time so it can be a little confusing.
I suggest you don't modify the bounds but use Auto-Layout instead. You should set the slider width to its superview height, and center the slider inside its superview. This way, when you rotate it, its height (after rotation) which is actually its width (before rotation) will be equal to its superview height. Centering the slider vertically will make sure the slider is touching the bottom and the top of its superview.
slider.widthAnchor.constraint(equalTo: sliderContainer.heightAnchor
slider.centerYAnchor.constraint(equalTo: sliderContainer.centerYAnchor)
slider.centerXAnchor.constraint(equalTo: sliderContainer.centerXAnchor)
If you want to place one of the 2 remaining sliders to the left or right of their superview, don't center it horizontally but do the following instead:
slider.leadingAnchor.constraint(equalTo: sliderContainer.leadingAnchor)
slider.trailingAnchor.constraint(equalTo: sliderContainer.trailingAnchor)
Also, carefully check your console for Auto-Layout error logs.
EDIT:
I checked the above code on a test project, here's my view controller code:
class ViewController: UIViewController {
#IBOutlet private weak var containerView: UIView!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let leftSlider = UISlider()
let centerSlider = UISlider()
let rightSlider = UISlider()
let colorSliders = [leftSlider, centerSlider, rightSlider]
var constraints = [NSLayoutConstraint]()
for slider in colorSliders {
slider.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(slider)
slider.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
constraints.append(slider.widthAnchor.constraint(equalTo: containerView.heightAnchor))
constraints.append(slider.centerYAnchor.constraint(equalTo: containerView.centerYAnchor))
slider.backgroundColor = .purple
}
constraints.append(leftSlider.centerXAnchor.constraint(equalTo: containerView.centerXAnchor))
constraints.append(centerSlider.centerXAnchor.constraint(equalTo: containerView.leadingAnchor))
constraints.append(rightSlider.centerXAnchor.constraint(equalTo: containerView.trailingAnchor))
NSLayoutConstraint.activate(constraints)
}
}
And here's what I got:

(as of July 7, 2017)
self.customSlider = [[UISlider alloc] init]];
self.customView = [[UIView alloc] init];
//create the custom auto layout constraints that you wish the UIView to have
[self.view addSubview:self.customView];
[self.customView addSubview:self.customSlider];
self.slider.transform = CGAffineTransformMakeRotation(-M_PI_2);
/*Create the custom auto layout constraints for self.customSlider to have these 4 constraints:
//1. self.customSlider CenterX = CenterX of self.customView
//2. self.customSlider CenterY = CenterY of self.customView
//3. self.customSlider width = self.customView height
//4. self.customSlider height = self.customView width
*/

Related

Buttons on UIScrollView bounces back when centered

My UIScrollView bounces back from left side, but stays normal from the right side.
Look at the gif please.
Important note: The buttons should be centered when the screen opens.
To center my buttons, I create them like this:
button.frame = CGRect(
x: screenWidth/2 + gap,
y: 0,
width: buttonWidth,
height: buttonHeight)
buttonsScrollView.addSubview(button)
I already set wide content size
buttonsScrollView.contentSize.width = screenWidth
and adding extra space - screenWidth + 100 does not help, it makes wider only from the right side.
You can do this with "calculated" frames or with auto-layout. In either case, the general idea...
Add your buttons to a UIView - we'll call it buttonsView:
Start the first button at gap distance from Zero, and set the frame of buttonsView to the height of the buttons, and add gap distance to the right-end.
Add buttonsView to the scroll view, at x: 0, y: 0.
Set the scroll view's .contentSize = buttonsView.frame.size.
Do all of that in viewDidLoad().
We can't center the buttonsView until we know the scroll view's frame width, which will likely vary depending on device (and if you rotate the device), so...
Add a class property to track the scroll view's width:
// track the scroll view frame width
var scrollViewWidth: CGFloat = 0
then, in viewDidLayoutSubviews() we know the scroll view's frame, so we'll adjust the .contentOffset.x to horizontally center the buttonsView:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// here we know frame size, but
// viewDidLayoutSubviews() can (and usually will) be called multiple times
// so we only want to execute this code when the scroll view width changes
if scrollViewWidth != scrollView.frame.width {
scrollViewWidth = scrollView.frame.width
// calculate content offset x so the row of buttons is centered horizontally
scrollView.contentOffset.x = (buttonsView.frame.width - scrollView.frame.width) * 0.5
}
}
Here's a complete sample implementation. No #IBOutlet connections -- everything is done via code -- so just set a view controller's class to CenterScrollViewController:
class CenterScrollViewController: UIViewController {
// create a scroll view
let scrollView: UIScrollView = {
let v = UIScrollView()
// background color so we can see its frame
v.backgroundColor = .systemYellow
return v
}()
// create a buttons holder view
let buttonsView: UIView = {
let v = UIView()
return v
}()
// gap between buttons and on left/right sides of button row
let gap: CGFloat = 12
// buttons will be (round) at 44 x 44 points
let btnSize: CGFloat = 44
// number of buttons
let numButtons: Int = 9
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(white: 0.9, alpha: 1.0)
title = "Calc"
// add scroll view to view
scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
// add buttons view to scroll view
scrollView.addSubview(buttonsView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain scroll view
// Top + 40
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
// Leading and Trailing
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor),
// Height equal to button height + "padding" on bottom
scrollView.heightAnchor.constraint(equalToConstant: btnSize + gap),
])
// let's add some buttons to the buttons view
var x: CGFloat = gap
for i in 1...numButtons {
let b = UIButton()
b.backgroundColor = .systemBlue
b.setTitle("\(i)", for: [])
b.setTitleColor(.white, for: .normal)
b.setTitleColor(.lightGray, for: .highlighted)
// let's keep the buttons square (1:1 ratio) so we can make them round
b.frame = CGRect(x: x, y: 0, width: btnSize, height: btnSize)
b.layer.cornerRadius = btnSize * 0.5
b.layer.borderColor = UIColor.black.cgColor
b.layer.borderWidth = 1
buttonsView.addSubview(b)
x += btnSize + gap
}
// x now equals the total buttons width plus gap on each side
// so set the frame size of the buttons view to (x, btnSize)
buttonsView.frame = CGRect(x: 0, y: 0, width: x, height: btnSize)
// set scroll view content size to the size of the buttons view
scrollView.contentSize = buttonsView.frame.size
}
// track the scroll view frame width
var scrollViewWidth: CGFloat = 0
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// here we know frame size, but
// viewDidLayoutSubviews() can (and usually will) be called multiple times
// so we only want to execute this code when the scroll view width changes
if scrollViewWidth != scrollView.frame.width {
scrollViewWidth = scrollView.frame.width
// calculate content offset x so the row of buttons is centered horizontally
scrollView.contentOffset.x = (buttonsView.frame.width - scrollView.frame.width) * 0.5
}
}
}

How to make Height and Width of ScrollView Still The same Before ZoomIn on UIImage?

Hello All iOS developer I'm new at iOS development. at the moment I work In the case I want to Zoom Picture, I use UIImageView in UIScrollView. Everything is Ok on ScrollView Height and Width but when I try to ZoomIn and ZoomOut to make to current Size of Image the UIScrollView has Increase the Height and Width. and I try to find out the solution but it's work.I need some help.thank you in advance.
This's The Problem
Here is my code
override func viewDidLoad() {
super.viewDidLoad()
initializeLanguages()
checkForm()
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
scrollView.delegate = self
lblRetake.textColor = TrueColor.trueColorbrand()
updateZoom()
scrollView.layer.cornerRadius = 10
scrollView.layer.masksToBounds = true
setShadowView(view: viewContainer)
imageView.isUserInteractionEnabled = true
addViewGesture()
displayImage()
}
//MARK: ZoomOut/ZoomIn Image
func updateZoom() {
let fwidth = self.imageView.image?.size.width ?? 0
let fheight = self.imageView.image?.size.height ?? 0
var minZoom = min(fwidth, fheight);
if (minZoom > 1) {
minZoom = 1;
}
self.scrollView.minimumZoomScale = minZoom;
self.scrollView.zoomScale = minZoom;
scrollView.maximumZoomScale = 10.0
}

UIStackView not changing the width

I have a UIStackView with UIImageViews that might be from 1 to 5. Each UIImageView has an image coming from an array (from previously downloaded and cached images) and I'd like to keep the UIImageViews stay in a perfect circle. I change the width constant of the UIStackView along with the spacing between the images in a way to overlap if there are more than 3 images.
I had writen this code in a previous project and it works perfectly fine, but for some reason, when I call the changeNavStackWidth function to change the width of the UIStackView, the width is not being updated and I have no clue why.
var userImages = [String]()
var navStackView : UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.alignment = .fill
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
return stack
}()
override func viewDidLoad() {
super.viewDidLoad()
setupNavStack()
navBarStackTapGesture()
}
func setupNavStack() {
guard let navController = navigationController else { return }
navController.navigationBar.addSubview(navStackView)
// x, y, w, h
navStackView.widthAnchor.constraint(equalToConstant: 95).isActive = true
navStackView.centerYAnchor.constraint(equalTo: navController.navigationBar.centerYAnchor).isActive = true
navStackView.heightAnchor.constraint(equalToConstant: 35).isActive = true
navStackView.centerXAnchor.constraint(equalTo: navController.navigationBar.centerXAnchor).isActive = true
}
func setNavBarImages() {
for image in userImages {
let imageView = UIImageView()
// imageView.image = UIImage(named: image)
let photoURL = URL(string: image)
imageView.sd_setImage(with: photoURL)
imageView.layer.borderColor = UIColor.white.cgColor
imageView.layer.borderWidth = 1
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
navStackView.addArrangedSubview(imageView)
navStackView.layoutIfNeeded()
}
switch userImages.count {
case 0:
print("0 images")
case 1:
changeNavStackWidth(constant: 35, spacing: 0)
//changeNavStackWidth(constant: 60, spacing: 0)
case 2:
changeNavStackWidth(constant: 80, spacing: 10)
case 3:
changeNavStackWidth(constant: 95, spacing: -5)
case 4:
changeNavStackWidth(constant: 110, spacing: -10)
case 5:
changeNavStackWidth(constant: 95, spacing: -20)
case 6...1000:
// changeNavStackWidth(constant: 95, spacing: -20)
default:
print("default")
}
navigationItem.titleView = navStackView
navStackView.layoutIfNeeded()
}
func changeNavStackWidth(constant: CGFloat, spacing: CGFloat) {
navStackView.constraints.forEach { constraint in
if constraint.firstAttribute == .width {
constraint.constant = constant
print("constan is:", constant) // not being printed
}
}
navStackView.spacing = spacing
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navStackView.subviews.forEach { $0.layer.cornerRadius = $0.frame.height / 2 }
}
based on our discussion in chat. Here is what you want:
A) if there is only one Image
B) if there are two Images:
C) if there are more than 2:
Code:
import UIKit
class TestStack: UIViewController{
var userImages = [#imageLiteral(resourceName: "images-1"),#imageLiteral(resourceName: "images-2"),#imageLiteral(resourceName: "images-4"),#imageLiteral(resourceName: "images-5")]
let imagesHolder: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
userImages.removeLast(2)
guard let bar = navigationController?.navigationBar else {return}
bar.addSubview(imagesHolder)
let size = 25
for i in 0..<userImages.count{
var x = i * size
x = userImages.count > 2 ? x/2 : x
x = (i == 1 && userImages.count == 2) ? x + 5 : x
let imageView = UIImageView(frame: CGRect(x: x, y: 0, width: size, height: size))
imageView.image = userImages[i]
imageView.clipsToBounds = true
imageView.layer.cornerRadius = CGFloat(size / 2)
imagesHolder.addSubview(imageView)
}
let width = CGFloat((userImages.count * size)/2)
NSLayoutConstraint.activate([
imagesHolder.centerYAnchor.constraint(equalTo: bar.centerYAnchor),
imagesHolder.centerXAnchor.constraint(equalTo: bar.centerXAnchor),
imagesHolder.widthAnchor.constraint(equalToConstant: width),
imagesHolder.heightAnchor.constraint(equalToConstant: CGFloat(size))
])
}
}
Instead of adjusting the StackView's width to adjust to the images:
Create a UIView and add the UIImageView as a subview to the UIView.
Add a top and bottom constraint to the UIImageView with the UIView
Add a center horizontally constraint the UIImageView with the UIView
Add an aspect ratio constraint to the UIImageView with itself and set that ratio to 1:1
Add the UIView to the Stack View
The constraints on the UIImageView in relation to the UIView will keep the images separated out equally, and the aspect ratio constraint on itself will keep the UIImage as a perfect circle.
Changing your imageView's content mode to .aspectFit will also do the trick without having to mess with your stackView's width.
Change this imageView.contentMode = .scaleAspectFill to this imageView.contentMode = .scaleAspectFit
AspectFill stretches the image to fit the view, and doesn't maintain the scale ratio of the image
AspectFit stretches the image until the horizontal bound OR vertical bound hits the view, while maintaining the scale ratio of the image.
see here

Facebook Picture zoom animation in swift, picture not showing the initial position

I've been trying to make a zoom animation like Facebook when you click a picture into a cell to move into the middle of the screen. The animation works, but for a reason that I can not figure out, it is not starting from the initial position it is giving me another frame. Please help, I've been struggling with this for a few days now.
I am using a collectionView with CustomCell and everything it's done programmatically:
The function in CenterVC:
//MARK: Function to animate Image View (it will animate to the middle of the View)
func animateImageView(statusImageView : UIImageView) {
//Get access to a starting frame
statusImageView.frame.origin.x = 0
if let startingFrame = statusImageView.superview?.convert(statusImageView.frame, to: nil) {
//Add the view from cell to the main view
let zoomImageView = UIView()
zoomImageView.backgroundColor = .red
zoomImageView.frame = statusImageView.frame
view.addSubview(zoomImageView)
print("Starting frame is: \(startingFrame)")
print("Image view frame is: \(statusImageView.frame)")
UIView.animate(withDuration: 0.75, animations: {
let height = (self.view.frame.width / startingFrame.width) * startingFrame.height
let y = self.view.frame.height / 2 - (height / 2)
zoomImageView.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: height)
})
}
}
This is the pictureView inside the cell and the constraints (this is where I am setting up the picture for the view, and I am using in cellForRowAtIndexPath cell.centerVC = self):
var centerVC : CenterVC?
func animate() {
centerVC?.animateImageView(statusImageView: pictureView)
}
let pictureView : UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.image = UIImage(named: "cat")
imageView.backgroundColor = UIColor.clear
imageView.layer.masksToBounds = true
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
addConstraintsWithFormat(format: "V:|-5-[v0(40)]-5-[v1]-5-[v2(200)]", views: profileImage, postTextView, pictureView)
addConstraintsWithFormat(format: "H:|[v0]|", views: pictureView)
This is what it prints out in the debugger:
Starting frame is: (5.0, 547.5, 365.0, 200.0)
Image view frame is: (0.0, 195.5, 365.0, 200.0)
As you can see the starting frame it's different from the initial frame and position of the picture. The animation it's not leaving the initial position it just appears somewhere on top and animates to the middle. I don't know what to do, please advice.
For some reason, it was not reading the starting frame rect so I've made a new CGRect that gets the origin and set the size of the picture: (it animates perfectly now)
zoomImageView.frame = CGRect(origin: startingFrame.origin, size: CGSize(width: view.frame.width, height: statusImageView.frame.height))

if we're in the real pre-commit handler we can't actually add any new fences due to CA restriction - could this mean something here?

I am trying to find why a collection of custom UIButtons does not work. In a version described in my earlier post I created a circle of UIButtons programmatically in Swift 3 and anchored the circle to the centre of the screen. That version used a subclass of UIView - based on an Apple Swift tutorial (Implement the Button Action) - together with an implementation of autolayout that draws on Imanou Petit’s excellent code examples (No.6 Anchor). In that version I managed to get my buttons to rotate successfully when the iPhone rotates but the button action-target fails to work.
So I have now tried an alternative version using a viewcontroller instead of a subclass of UIView. This time the same button action-target works but rotating the phone causes the image to shift away from the centre as shown below.
With each rotation the following message also appears twice in the debug area of Xcode.
***[App] if we're in the real pre-commit handler we can't
actually add any new fences due to CA restriction***
The message happens three times out of four, i.e. there is no message when the phone is turned upside down. This occurs when I run either the code in my previous post or the code shown below. And in each case it made no difference whether the Upside Down box was checked or un-checked.
I also tried disabling OS_ACTIVITY MODE but that changed nothing except hide a message that might potentially explain the problem. Someone more experienced than me will hopefully recognise what this debug message means either in the context of my previous code (shown here) or my latest code, shown below.
ORIGINAL CODE
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")
}
}
I am still in the process of learning Swift so it doesn’t matter at this stage whether the solution uses a viewcontroller or a subclass of UIView so long as I can arrange a circle of UIButtons that still work after I configure them using autolayout. Every suggestion is welcome. Thanks.
SOLUTION
The message that appeared in Xcode’s debug area - and which I used in the subject line of this post - was clearly not the issue. Thanks to Rob Mayoff, NSLayoutConstraint now computes the dimensions and position of each button whereas these were computed prior to run-time in my original code. His solution along with several other improvements are now reflected in the code below. To this I added the original action-target for the buttons. These not only work but remain locked to the centre of the view whenever the device orientation changes.
The code can easily be made to work for a different size configuration by changing values for radius, buttonCount and buttonSideLength (see table).
Here is the code
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 }()
}
Don't worry about the fences warning message. It seems to be harmless and not caused by anything you're doing.
There are several problems with the code you posted:
You create myView and constrain its center, but you don't give it any size constraints. Furthermore, myView is a local variable and you don't add any subviews to myView. So myView is an invisible, sizeless view with no contents. Why are you creating it at all?
You're drawing your “uberCircle” using a bare shape layer. By “bare”, I mean there's no view whose layer property is that layer. Bare layers don't participate in autolayout.
You compute the position of each button based on the center of the top-level view's bounds. You're doing this during viewDidLoad, but viewDidLoad is called before the top-level view has been resized to fit the current device. So your wheel won't even be centered at launch on some devices.
You don't set any constraints on the buttons, or set their autoresizing masks. The result is that, when the device rotates, the top-level view resizes but each button's position (relative to the top-left corner of the top-level view) stays the same.
Turning on the “Upside Down” checkbox is not sufficient to allow upside-down orientation on iPhones, only on iPads.
Here are the changes you need to make:
Use a view to draw the “uberCircle”. If you want to use a shape layer, make a subclass of UIView that uses a CAShapeLayer for its layer. You can copy the ShapeView class from this answer.
Set constraints from the center of the uberCircle to the center of the top-level view, to keep the uberCircle centered when the top-level view changes size.
For each button, set constraints from the center of the button to the center of the top-level view, to keep the button positioned properly when the top-level view changes size. These constraints need non-zero constants to offset the buttons from the center.
Override supportedInterfaceOrientations to enable upside-down orientation (in addition to checking the “Upside Down” checkbox).
Get rid of myView in viewDidLoad. You don't need it.
Get rid of the centre property. You don't need it.
Thus:
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 }()
}
I'd just constrain the parent UIView and constrain all of the buttons to the center of the parent view and override layoutSubviews. In layout subviews you can manipulate .constant of the centerX and centerY constraints for the buttons to reposition them. Alternatively you can just center all of them and use the .transform property of each button to move them into place.