Creating view with overlapping circle - ios

I'm trying to create a simple UIView line and then a circle on top if it. i'm however not sure how i'm suppose to draw the circle without making hierarchy problems? so far i have below, which jut creates the line
line
self.stepLine = UIView()
self.stepLine.translatesAutoresizingMaskIntoConstraints = false
stepView.addSubview(self.stepLine)
self.stepLine.backgroundColor = Color.theme.value
self.stepLine.bottomAnchor.constraint(equalTo: stepView.bottomAnchor, constant: -4).isActive = true
self.stepLine.heightAnchor.constraint(equalToConstant: 6).isActive = true
self.stepLine.leftAnchor.constraint(equalTo: stepView.leftAnchor).isActive = true
override func layoutSubviews() {
self.stepLine.widthAnchor.constraint(equalToConstant: self.frame.width/2).isActive = true
}
Illustration

let circleView = UIView()
circleView.layer.cornerRadius = 10 // half of the width/height dimension
circleView.translatesAutoresizingMaskIntoConstraints = false
stepView.addSubview(circleView)
circleView.centerYAnchor.constraint(equalTo: stepLine.centerYAnchor).isActive = true
circleView.trailingAnchor.constraint(equalTo: stepLine.trailingAnchor).isActive = true
circleView.widthAnchor.constraint(equalToConstant: 20).isActive = true
circleView.heightAnchor.constraint(equalToConstant: 20).isActive = true
Or some variation thereof. But this is the general concept I would suggest given the nature of the code you presented.

Related

Can't set UIView's background color Swift 5

I have a view that I initialise like this:
var view: UIView! = {
var perm = UIView()
perm.backgroundColor = UIColor.black
// enable auto layout
perm.translatesAutoresizingMaskIntoConstraints = false
return perm
}()
After that I add a label as a subview to view. The label is initialised like this:
var title: UILabel! = {
let perm = UILabel()
perm.textColor = UIColor.white
perm.numberOfLines = 0
perm.font = UIFont.boldSystemFont(ofSize: 20)
// enable auto layout
perm.translatesAutoresizingMaskIntoConstraints = false
return perm
}()
After that I add programmatically some constraints. When I run the app the title is displayed in the right position, but the view's background color has not been set.
EDIT
This is how I've set the constraints:
view.addSubview(title)
view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: image.bottomAnchor).isActive = true
title.leadingAnchor.constraint(equalTo: title.superview!.leadingAnchor, constant: 20).isActive = true
title.trailingAnchor.constraint(equalTo: title.superview!.trailingAnchor, constant: -20).isActive = true
title.bottomAnchor.constraint(equalTo: title.superview!.bottomAnchor, constant: -20).isActive = true
view needs a height
view.heightAnchor.constraint(equalToConstant:200).isActive = true
OR For label height plus top and bottom padding 40
title.topAnchor.constraint(equalTo:view.topAnchor, constant:20).isActive = true

UIScrollView doesn't respond to gestures

I have a UIScrollView which is a dashboard basically with relevant information for the user. It has a pager underneath and contains three UIViews at the moment.
I used to set the different views with a CGRect as the frame, setting the x offset to the width of the UIScrollView * the page number. This worked fine, but it was a hassle to make the UIScrollView disappear when scrolling in the UITableView underneath.
I am now using AutoLayout with constraints to display the information, which works fine. However, my UIScrollView is not scrolling anymore. It does not respond to any swipe gestures I perform. However, it does respond to taps in the pager, which shows me the offsets and constraints are right, I am just unable to scroll in it. Take a look:
My UIScrollView is made up like this:
var dashboardView: UIScrollView = {
let dashboardView = UIScrollView()
dashboardView.translatesAutoresizingMaskIntoConstraints = false
dashboardView.backgroundColor = UIColor.clear
dashboardView.layer.masksToBounds = true
dashboardView.layer.cornerRadius = 10
dashboardView.isPagingEnabled = true
dashboardView.showsHorizontalScrollIndicator = false
dashboardView.showsVerticalScrollIndicator = false
dashboardView.alwaysBounceHorizontal = false
dashboardView.alwaysBounceVertical = false
return dashboardView
}()
I then set the different views like so:
for index in 0..<3 {
let currentDash = UIView()
currentDash.backgroundColor = UIColor.lightGray
currentDash.layer.cornerRadius = 10
currentDash.layer.masksToBounds = false
currentDash.translatesAutoresizingMaskIntoConstraints = false
currentDash.isUserInteractionEnabled = false
dashboardView.addSubview(currentDash)
currentDash.topAnchor.constraint(equalTo: dashboardView.topAnchor).isActive = true
currentDash.bottomAnchor.constraint(equalTo: dashboardView.bottomAnchor).isActive = true
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.trailingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: (dashboardWidth * CGFloat(index)) + dashboardWidth).isActive = true
currentDash.widthAnchor.constraint(equalToConstant: dashboardWidth).isActive = true
currentDash.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
}
When I remove the constraints (AutoLayout) and set it with frames it works perfectly, like this:
dashFrame.origin.x = dashboardWidth * CGFloat(index)
dashFrame.size = CGSize(width: dashboardWidth, height: dashboardHeight)
let currentDash = UIView(frame: dashFrame)
But then, because of setting the frame, the view doesn't scroll up as I would, which is why I want to use AutoLayout.
Any ideas or suggestions as to what I am doing wrong? My ScrollViewDidScroll() method simply gets not called, because a simple print doesn't return anything in my console.
I have tried setting the isUserInteractionEnabled property to false on the currentDash UIView, which was no success. I have also tried removing all constraints separately, some together, etc. - but also this doesn't work - I need a topAnchor, bottomAnchor, heightAnchor and widthAnchor at least.
Oh, and, of course, I set the contentSize property:
dashboardView.contentSize = CGSize(width: dashboardWidth * CGFloat(dashboardPager.numberOfPages), height: dashboardHeight)
Any suggestions or hints towards the right answer would be deeply appreciated. Thanks!
EDIT: I also set constraints for the UIScrollView itself:
dashboardView.topAnchor.constraint(equalTo: dashboardBG.topAnchor).isActive = true
dashboardView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: normalSpacing).isActive = true
dashboardView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -normalSpacing).isActive = true
dashboardView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
dashboardView.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
dashboardView.delegate = self
The issue is here
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.trailingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: (dashboardWidth * CGFloat(index)) + dashboardWidth).isActive = true
1- Regarding leading of the views , the leading of the first view should be pinned to the scrollView , the leading of the second view pinned to the trailing of the previous and so on , so
if index == 0{
// make leading with scrolview leading
else
{
// make leading with prevView trailing
}
So make a var initiated with scrollView and change end of for loop
2- Regarding the trailing , only the last view trailing be pinned to the trailing of the scrollview
if index == 2 { // last
// make trailing with scrollView
}
I eventually figured it out thanks to Sh_Khan’s answer. I held on to the original code, but removed the trailingAnchor for all indexes and added it only to the last index.
for index in 0..<3 {
let currentDash = UIView()
currentDash.backgroundColor = UIColor.lightGray
currentDash.layer.cornerRadius = 10
currentDash.layer.masksToBounds = false
currentDash.translatesAutoresizingMaskIntoConstraints = false
currentDash.isUserInteractionEnabled = false
dashboardView.addSubview(currentDash)
currentDash.topAnchor.constraint(equalTo: dashboardView.topAnchor).isActive = true
currentDash.bottomAnchor.constraint(equalTo: dashboardView.bottomAnchor).isActive = true
currentDash.leadingAnchor.constraint(equalTo: dashboardView.leadingAnchor, constant: dashboardWidth * CGFloat(index)).isActive = true
currentDash.widthAnchor.constraint(equalToConstant: dashboardWidth).isActive = true
currentDash.heightAnchor.constraint(equalToConstant: dashboardHeight).isActive = true
if index == 2 {
currentDash.trailingAnchor.constraint(equalTo: dashboardView.trailingAnchor).isActive = true
}
}
Thanks for the help!

How to account for different screen sizes when writing layout code programmatically

I'm creating the screen for a login screen, I first tried to build using the storyboard, but it got really confusing very fast. So I jumped to the code, the code I wrote is for an Image, Text, two text fields, and three buttons to show in a simple view similar to a stack view.
Here's an example of two different on the right is the iPhone SE and on the left is the iPhone XR.
The layout looks fine for the SE but really small for the XR. How do I account for the screen changes so that the UI looks the same on all devices?
Here's the code I used for adding the constraints programmatically:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Round the edges
Login.layer.cornerRadius = 4
Register.layer.cornerRadius = 4
logo.translatesAutoresizingMaskIntoConstraints = false
logo.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logo.widthAnchor.constraint(equalToConstant: 200).isActive = true
logo.heightAnchor.constraint(equalToConstant: 80).isActive = true
logo.topAnchor.constraint(equalTo: view.topAnchor, constant: 90).isActive = true
logo.contentMode = .scaleAspectFit
letsloginTextField.translatesAutoresizingMaskIntoConstraints = false
letsloginTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
letsloginTextField.bottomAnchor.constraint(equalTo: logo.bottomAnchor, constant: 50).isActive = true
emailTextField.translatesAutoresizingMaskIntoConstraints = false
emailTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
emailTextField.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
emailTextField.widthAnchor.constraint(equalToConstant: 180).isActive = true
emailTextField.heightAnchor.constraint(equalToConstant: 30).isActive = true
//emailTextField.bottomAnchor.constraint(equalTo: letsloginTextField.bottomAnchor, constant: 50).isActive = true
passwordTextField.translatesAutoresizingMaskIntoConstraints = false
passwordTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
passwordTextField.widthAnchor.constraint(equalToConstant: 180).isActive = true
passwordTextField.heightAnchor.constraint(equalToConstant: 30).isActive = true
passwordTextField.bottomAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 50).isActive = true
Login.translatesAutoresizingMaskIntoConstraints = false
Login.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
Login.widthAnchor.constraint(equalToConstant: 100).isActive = true
Login.heightAnchor.constraint(equalToConstant: 30).isActive = true
Login.bottomAnchor.constraint(equalTo: passwordTextField.bottomAnchor, constant: 60).isActive = true
Register.translatesAutoresizingMaskIntoConstraints = false
Register.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
Register.widthAnchor.constraint(equalToConstant: 100).isActive = true
Register.heightAnchor.constraint(equalToConstant: 30).isActive = true
Register.bottomAnchor.constraint(equalTo: Login.bottomAnchor, constant: 60).isActive = true
// set delegates
GIDSignIn.sharedInstance().uiDelegate = self
//GIDSignIn.sharedInstance().signIn()
}
You could set the width of the views relative to the screen width like to following:
let width = UIScreen.main.bounds.size.width * 0.3
emailTextField.widthAnchor.constraint(equalToConstant: width).isActive = true
and then you set a ratio for your views. for eg: the height is 1:2 the width
emailTextField.heightAnchor.constraint(equalToConstant: width * 0.5).isActive = true
Change the values according to your need.
I strongly suggest getting into XCode Interface Builder:
https://developer.apple.com/videos/developer-tools/interface-builder
Furthermore, you need to change your way of thinking how to setup an UI for a mobile Device.
For example: In your case this means instead of defining width constraints for the Login and Register Button - you may define leading and trailing constraints from the Button-border to the border of the UIViewController and perhaps just set a fixed height constraint for them. This will make the Buttons dynamically adapt to the Device.
The next suggestion would be to avoid layouting this elements within code. Interface Builder makes it really easy to define these layouts and you will get a lot of handy features for free, furthermore it also can handle more complex layouts. Your code will become unmanageable and hard to read/change if the UI will get more complex. Furthermore it will introduce a lot of Boilerplate code.
In this case you need to playing with screen size.
1. Get screen width
`let screenWidth = UIScreen.main.bounds.size.width
2. Set margin of button (left, right)
let margin = 60
Note : left margin = 30, right margin = 30 total Margin = 60, you can change margin as per your design requirements.
3. Set our button size
New code
Register.widthAnchor.constraint(equalToConstant:screenWidth - margin).isActive = true
Your old code
Register.widthAnchor.constraint(equalToConstant: 100).isActive = true
Note : This is only for portrait mode.

How to make auto-layout constraint dependent on multiple other anchors?

How can auto-layout be used to make a view's height equal to the sum of two other view's heights?
For example:
viewA.heightAnchor.constraint(equalTo: ...),
viewB.heightAnchor.constraint(equalTo: ...),
viewC.heightAnchor.constraint(equalTo: viewA.heightAnchor + viewB.heightAnchor)
Is there a solution that does not involve setting a constant value and recalculating it on every view bounds change?
You can, but only with the help of some helper views, I think. See this example playground I just now cooked up that achieves this:
import Foundation
import UIKit
import PlaygroundSupport
let vc = UIViewController()
vc.view = UIView()
vc.view.backgroundColor = .white
let viewA = UIView()
let viewB = UIView()
let viewC = UIView()
viewA.backgroundColor = .red
viewB.backgroundColor = .green
viewC.backgroundColor = .blue
viewA.translatesAutoresizingMaskIntoConstraints = false
viewB.translatesAutoresizingMaskIntoConstraints = false
viewC.translatesAutoresizingMaskIntoConstraints = false
vc.view.addSubview(viewA)
vc.view.addSubview(viewB)
vc.view.addSubview(viewC)
let helperViewA = UIView()
let helperViewB = UIView()
helperViewA.translatesAutoresizingMaskIntoConstraints = false
helperViewB.translatesAutoresizingMaskIntoConstraints = false
helperViewA.isHidden = true
helperViewB.isHidden = true
vc.view.addSubview(helperViewA)
vc.view.addSubview(helperViewB)
viewA.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
viewA.leadingAnchor.constraint(equalTo: vc.view.leadingAnchor).isActive = true
viewA.trailingAnchor.constraint(equalTo: viewB.leadingAnchor).isActive = true
viewA.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
viewB.bottomAnchor.constraint(equalTo: vc.view.bottomAnchor).isActive = true
viewB.widthAnchor.constraint(equalTo: viewA.widthAnchor, multiplier: 1.0).isActive = true
viewB.trailingAnchor.constraint(equalTo: vc.view.trailingAnchor).isActive = true
viewB.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
viewA.heightAnchor.constraint(equalTo: helperViewA.heightAnchor, multiplier: 1.0).isActive = true
viewB.heightAnchor.constraint(equalTo: helperViewB.heightAnchor, multiplier: 1.0).isActive = true
helperViewA.bottomAnchor.constraint(equalTo: helperViewB.topAnchor).isActive = true
viewC.widthAnchor.constraint(equalToConstant: 100).isActive = true
viewC.topAnchor.constraint(equalTo: helperViewA.topAnchor).isActive = true
viewC.bottomAnchor.constraint(equalTo: helperViewB.bottomAnchor).isActive = true
helperViewA.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
helperViewA.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
helperViewB.leadingAnchor.constraint(equalTo: viewC.leadingAnchor).isActive = true
helperViewB.trailingAnchor.constraint(equalTo: viewC.trailingAnchor).isActive = true
viewC.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true
viewC.centerYAnchor.constraint(equalTo: vc.view.centerYAnchor).isActive = true
vc.view.frame.size = CGSize(width: 375, height: 667)
PlaygroundPage.current.liveView = vc.view
The idea is that you have two helper views stacked vertically on each other, connected by the top one's bottomAnchor and the bottom one's topAnchor. You then set each of these helper views' heights to be equal to your viewA and viewB heights. Then, your viewC can be attached to the top view's topAnchor and the bottom view's bottomAnchor, which gives the result of viewC being the height of viewA's height plus viewB's height.

Set X and Y values of UILabel using NSLayout

I am adding a UI Label and I am trying out programmatic UI code. I set up my label using this code:
let titleLabel: UILabel = {
let lb = UILabel()
lb.translatesAutoresizingMaskIntoConstraints = false
lb.textAlignment = .center
lb.numberOfLines = 1
lb.textColor = UIColor.black
lb.font=UIFont.systemFont(ofSize: 22)
lb.backgroundColor = UIColor.white
return lb
and then added constraints using this code:
func setupTitleLabel() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.heightAnchor.constraint(equalToConstant: 100).isActive = true
titleLabel.widthAnchor.constraint(equalToConstant: 200).isActive = true
titleLabel.centerXAnchor.constraint(equalTo: titleLabel.superview!.centerXAnchor).isActive = true
titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.centerYAnchor).isActive = true
}
I call this function later on. However, I am not sure how to change to X and Y values. Currently, it is set to the middle of the page for both X and Y, but how would I move it up to the top.
As I said in my comment, you are using titleLabel.superview!.centerYAnchor wrongly if you need pin your label to top of your label superview you need use titleLabel.superview!.topAnchor instead
replace this
titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.centerYAnchor).isActive = true
by this
titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.topAnchor).isActive = true
What I understand the case is that the label is in the centerX and centerY, and you want it to be at the top of the superView later.If so,I think you can use var centerYAnchor: NSLayoutConstraint?
centerYAnchor = titleLabel.centerYAnchor.constraint(equalTo: titleLabel.superview!.centerYAnchor)
centerYAnchor.isActive = true
And when you want to change the label at the top later,you can set centerYAnchor.isActive = false and set the label's topAnchor.
titleLabel.topAnchor.constraint(equalTo: titleLabel.superview!.topAnchor).isActive = true

Resources