Align UIImage vertically center - ios

I am trying to align an image vertically central using Swift. I understand you do this by using constraints, however I've been unable to get this to work.
func getLogo() {
let logo = UIImage(named: "LogoWhite")
let logoView = UIImageView(image: logo)
logoView.contentMode = .scaleAspectFit
self.addSubview(logoView)
}

If you don't want to use constraints (I personally do not like them) you can check for container center and put your UIImageView there.
Example:
containerView -> the view that contains your logo
logo -> the view you want vertically centered
logo.center.y = containerView.center.y
If the containerView is the screen, then
let screen = UIScreen.main.bounds
let height = screen.height
logo.center.y = screen.height / 2

I would suggest using extensions on UIView to make auto layout much easier. This seems like a lot of code to begin with for such a simple task but these convenience functions will make things a lot quicker in the long run for example:
public func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero){
translatesAutoresizingMaskIntoConstraints = false
//Set top, left, bottom and right constraints
if let top = top {
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading {
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let bottom = bottom {
bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom).isActive = true
}
if let trailing = trailing {
trailingAnchor.constraint(equalTo: trailing, constant: -padding.right).isActive = true
}
//Set size contraints
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
Then you could simply call:
someUIView.anchor(anchor(top: topAnchor, leading: leadingAnchor, bottom: bottomAnchor, trailing: nil, padding: .init(top: 16, left: 16, bottom: 16, right: 16), size: .init(width: 80, height: 80))
Then to answer your question directly you could then add more extensions to do some stuff easily:
public func anchorCenterXToSuperview(constant: CGFloat = 0) {
translatesAutoresizingMaskIntoConstraints = false
if let anchor = superview?.centerXAnchor {
centerXAnchor.constraint(equalTo: anchor, constant: constant).isActive = true
}
}
Finally you could then simply call anchorCenterXToSuperview() on any UIView to centre any object.
Don't forget to make sure you've added your view to a view hierarchy before attempting to layout your views otherwise you'll get errors.

Related

How to resize views for multiple iOS screen sizes using auto layout programmatically

I am using an extension for my auto-layout as such:
extension UIView{
func anchorSize(to view: UIView){
widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero){
translatesAutoresizingMaskIntoConstraints = false
if let top = top{
topAnchor.constraint(equalTo: top, constant: padding.top).isActive = true
}
if let leading = leading{
leadingAnchor.constraint(equalTo: leading, constant: padding.left).isActive = true
}
if let trailing = trailing{
trailingAnchor.constraint(equalTo: trailing, constant: padding.right).isActive = true
}
if let bottom = bottom{
bottomAnchor.constraint(equalTo: bottom, constant: padding.bottom).isActive = true
}
if size.width != 0 {
widthAnchor.constraint(equalToConstant: size.width).isActive = true
}
if size.height != 0 {
heightAnchor.constraint(equalToConstant: size.height).isActive = true
}
}
}
However, I've come across an issue in which when displaying my views on different screen sizes the views become squished or simply don't fit. How would I go about resizing the views through auto-layout to fit multiple screen sizes? This is also the code for when I apply the constraints to something like a label:
logo.anchor(top: view.safeAreaLayoutGuide.topAnchor, leading: view.safeAreaLayoutGuide.leadingAnchor, bottom: nil, trailing: view.safeAreaLayoutGuide.trailingAnchor, padding: .init(top: 20, left: 0, bottom: 0, right: 0))
logo.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
subLogo.anchor(top: logo.bottomAnchor, leading: logo.leadingAnchor, bottom: nil, trailing: logo.trailingAnchor, padding: .init(top: 20, left: 0, bottom: 0, right: 0))
subLogo.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor).isActive = true
The iPhone 11 is the correct way of how I would auto layout. I really want to universally resize all my views for other devices.
Here are the screenshots of the example devices
Thanks in advance.
If you can show what you are actually talking about from screenshots, would help me a lot to see what’s going on.
By the way you are missing translatesAutoresizingMaskIntoConstraints = false in func anchorSize(). Also what are you trying to achieve by having a negative padding amount for top?

mapKit error as Value of type 'UIImageView' has no member 'anchor'

var imageView: UIImageView {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.image = UIImage(named: "locationPin")
return iv
}
//MARK:- Init
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
}
//MARK:- Helper function
func configureViewComponents() {
view.backgroundColor = .white
view.addSubview(imageView)
// below I am getting error as
Value of type 'UIImageView' has no member 'anchor'
imageView.anchor(top: view.topAnchor, left: nil, bottom: nil, right: nil, paddingTop: 140, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 200, height: 200)
}
}
I tried to implement the popup enable location screen programmatically with an imageview but I am getting the error as "Value of type 'UIImageView' has no member 'anchor'"
You should have
extension UIView {
func anchor(} {}
}
Found Here
imageViewtranslatesAutoresizingMaskIntoConstraints = false
view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 140.0 ),
imageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10 ),
imageView.widthAnchor.constraint(equalToConstant:200),
imageView.heightAnchor.constraint(equalToConstant: 200)
])
With extension
enum ConstraintType {
case top, leading, trailing, bottom, width, height
}
extension UIView {
func anchor(top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets = .zero, size: CGSize = .zero) {
//translate the view's autoresizing mask into Auto Layout constraints
translatesAutoresizingMaskIntoConstraints = false
var constraints: [ConstraintType : NSLayoutConstraint] = [:]
if let top = top {
constraints[.top] = topAnchor.constraint(equalTo: top, constant: padding.top)
}
if let leading = leading {
constraints[.leading] = leadingAnchor.constraint(equalTo: leading, constant: padding.left)
}
if let bottom = bottom {
constraints[.bottom] = bottomAnchor.constraint(equalTo: bottom, constant: -padding.bottom)
}
if let trailing = trailing {
constraints[.trailing] = trailingAnchor.constraint(equalTo: trailing, constant: -padding.right)
}
if size.width != 0 {
constraints[.width] = widthAnchor.constraint(equalToConstant: size.width)
}
if size.height != 0 {
constraints[.height] = heightAnchor.constraint(equalToConstant: size.height)
}
let constraintsArray = Array<NSLayoutConstraint>(constraints.values)
NSLayoutConstraint.activate(constraintsArray)
}
}
class ViewController: UIViewController {
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFit
iv.image = UIImage(named: "locationPin")
return iv
}()
// MARK: - View Life Cycle Methods
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imageView)
imageView.anchor(top:view.topAnchor, leading: view.leadingAnchor, bottom: nil, trailing: nil, padding: UIEdgeInsets.init(top: 140, left: 0, bottom: 0, right: 0), size: CGSize.init(width: 200, height: 200))
}
}

Is it possible to use Auto Layout in a UITextField's leftView?

I want to customize a UITextField's leftView with a view that is automatically sized depending on its contents:
func set(leftImage image: UIImage) {
let imageView = UIImageView(image: image)
let paddingContainer = UIView()
// This is the crucial point:
paddingContainer.translatesAutoresizingMaskIntoConstraints = false
paddingContainer.addSubview(imageView)
imageView.pin(toMarginsOf: paddingContainer)
leftView = paddingContainer
leftViewMode = .always
}
where the pin method just pins the image view on all four sides to the margins of the paddingContainer:
func pin(toMarginsOf view: UIView) {
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor),
leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor)
])
}
On iOS 12, everything works as expected, but on iOS versions < 12, the image is completely misplaced. It's not even within the bounds of the text field but in the upper left corner of my view controller's view.
To me it seems like older versions of iOS don't support using Auto Layout inside the view that you set as a text field's leftView. The documentation states:
The left overlay view is placed in the rectangle returned by the leftViewRect(forBounds:) method of the receiver.
but it doesn't state how it's placed there: By using constraints or by setting the frame directly.
Are there any reliable sources or educated guesses if using Auto Layout is supported at all for the leftView?
extension UITextField{
func setLeft(image: UIImage, withPadding padding: CGFloat = 0) {
let wrapperView = UIView.init(
frame: CGRect.init(
x: 0,
y: 0,
width: bounds.height,
height: bounds.height
)
)
let imageView = UIImageView()
imageView.image = image
imageView.contentMode = .scaleAspectFit
wrapperView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.leadingAnchor.constraint(
equalTo: wrapperView.leadingAnchor,
constant: padding
),
imageView.trailingAnchor.constraint(
equalTo: wrapperView.trailingAnchor,
constant: -padding
),
imageView.topAnchor.constraint(
equalTo: wrapperView.topAnchor,
constant: padding
),
imageView.bottomAnchor.constraint(
equalTo: wrapperView.bottomAnchor,
constant: -padding
)
])
leftView = wrapperView
leftViewMode = .always
}
}
hope this will help
You can set its frame on layout subviews function like this
override func layoutSubviews() {
super.layoutSubviews()
if let lv = self.leftView {
lv.frame = CGRect(x: 0, y: 0, width: self.bounds.height, height: self.bounds.height)
}
}

Easiest way for assigning constraints to a view in swift?

I use the following method to add a view as subview and adding its constraints programatically.
This is how I create the view:
// In class
let view: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
And this is how I add it's constraints:
addSubview(view)
view.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
view.widthAnchor.constraint(equalToConstant: 100).isActive = true
view.heightAnchor.constraint(equalToConstant: 100).isActive = true
Is there any method in which I can achieve this easier or with less lines of code. I need constraints to be created programatically and I don't recommend using another library just for this purpose.
It's not fewer lines of code, but rather than .isActive = true, line-by-line, I find activate to be a little cleaner:
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: leadingAnchor),
view.trailingAnchor.constraint(equalTo: trailingAnchor),
view.widthAnchor.constraint(equalToConstant: 100),
view.heightAnchor.constraint(equalToConstant: 100)
])
Or, if you're doing this a lot, write your own extension:
extension UIView {
func activate(leading: NSLayoutAnchor<NSLayoutXAxisAnchor>? = nil,
trailing: NSLayoutAnchor<NSLayoutXAxisAnchor>? = nil,
top: NSLayoutAnchor<NSLayoutYAxisAnchor>? = nil,
bottom: NSLayoutAnchor<NSLayoutYAxisAnchor>? = nil,
centerX: NSLayoutAnchor<NSLayoutXAxisAnchor>? = nil,
centerY: NSLayoutAnchor<NSLayoutYAxisAnchor>? = nil,
width: CGFloat? = nil,
height: CGFloat? = nil) {
translatesAutoresizingMaskIntoConstraints = false
if let leading = leading { leadingAnchor.constraint(equalTo: leading).isActive = true }
if let trailing = trailing { trailingAnchor.constraint(equalTo: trailing).isActive = true }
if let top = top { topAnchor.constraint(equalTo: top).isActive = true }
if let bottom = bottom { bottomAnchor.constraint(equalTo: bottom).isActive = true }
if let centerX = centerX { centerXAnchor.constraint(equalTo: centerX).isActive = true }
if let centerY = centerY { centerYAnchor.constraint(equalTo: centerY).isActive = true }
if let width = width { widthAnchor.constraint(equalToConstant: width).isActive = true }
if let height = height { heightAnchor.constraint(equalToConstant: height).isActive = true }
}
}
And then you can do it with one line of code:
view.activate(leading: leadingAnchor, trailing: trailingAnchor, width: 100, height: 100)
You could use visual format language. In doing so you gain conciceness but lose clarity to developers who don't understand the syntax.
If you want to setup the above constraints for a view using VFL, the code would be as follows.
var constraints = [NSLayoutConstraint]()
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[view(100)]|", options: [], metrics: nil, views: ["view":view])
constraints += NSLayoutConstraint.constraints(withVisualFormat: "V:[view(100)]", options: [], metrics: nil, views: ["view":view])
NSLayoutConstraint.activate(constraints)
As you can see, for such a simple set of constraints, you don't gain a lot. However, consider you have to build a complex UI with many constraints on different objects, all relating to eachother, VFL would make doing so far easier and more concise.
VFL is very much a love it or hate it kind of thing, though I would recommend doing some reading on it so you can make your informed decision. This RayWendelich guide is particularly useful.
I wrote down a few extensions that I think is going to help all developers.
extension UIView {
func addView(view: UIView, top: NSLayoutYAxisAnchor?, leading: NSLayoutXAxisAnchor?, bottom: NSLayoutYAxisAnchor?, trailing: NSLayoutXAxisAnchor?, padding: UIEdgeInsets? = .zero, width: CGFloat?, height: CGFloat?) {
view.translatesAutoresizingMaskIntoConstraints = false
if !self.subviews.contains(view) {
addSubview(view)
}
if let top = top {
view.topAnchor.constraint(equalTo: top, constant: padding?.top ?? 0).isActive = true
}
if let leading = left {
view.leadingAnchor.constraint(equalTo: leading, constant: padding?.left ?? 0).isActive = true
}
if let bottom = bottom {
view.bottomAnchor.constraint(equalTo: bottom, constant: -(padding?.bottom ?? 0)).isActive = true
}
if let trailing = right {
view.trailingAnchor.constraint(equalTo: trailing, constant: -(padding?.right ?? 0)).isActive = true
}
if let width = width, width != 0 {
view.widthAnchor.constraint(equalToConstant: width).isActive = true
}
if let height = height, height != 0 {
view.heightAnchor.constraint(equalToConstant: height).isActive = true
}
}
func anchorSizeTo(width: NSLayoutDimension?,and height: NSLayoutDimension?) {
if let width = width {
self.widthAnchor.constraint(equalTo: width).isActive = true
}
if let height = height {
self.heightAnchor.constraint(equalTo: height).isActive = true
}
}
func anchorCenterTo(x: NSLayoutXAxisAnchor?, y: NSLayoutYAxisAnchor?,with padding: CGPoint = .zero) {
if let anchor = x {
self.centerXAnchor.constraint(equalTo: anchor, constant: padding.x).isActive = true
}
if let anchor = y {
self.centerYAnchor.constraint(equalTo: anchor, constant: padding.y).isActive = true
}
}
}
Usage:
superView.addView(view: subView, top: upperView.bottomAnchor, leading: leftView.trailingAnchor, bottom: superView.bottom, trailing: rightView.trailingAnchor, padding: UIEdgeInsetsMake(12, 12, 12, 12), width: 0, height: nil)
NSLayoutAnchor class has made writing AutoLayout code much easier but still it's verbose and repetitive. You can create extension on UIView and add wrapper around AutoLayout which can be used from all UIViewControllers. For example I have added methods for size and pinning edges below
extension UIView {
func size(width: CGFloat, height: CGFloat) {
NSLayoutConstraint.activate([
self.widthAnchor.constraint(equalToConstant: width),
self.heightAnchor.constraint(equalToConstant: height)
])
}
func edges(_ edges: UIRectEdge, to view: UIView, offset: UIEdgeInsets) {
if edges.contains(.top) || edges.contains(.all) {
self.topAnchor.constraint(equalTo: view.topAnchor, constant: offset.top).isActive = true
}
if edges.contains(.bottom) || edges.contains(.all) {
self.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: offset.bottom).isActive = true
}
if edges.contains(.left) || edges.contains(.all) {
self.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: offset.left).isActive = true
}
if edges.contains(.right) || edges.contains(.all) {
self.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: offset.right).isActive = true
}
}
}
now you can set constraints for the your view using just 2 lines
view.edges([.left, .right], to: self.view, offset: .zero)
view.size(width: 100, height: 100)
Besides from NSLayoutConstraint.activate method that Rob mentioned, you can use a more general approach to eliminate multiple .isActive = true statements:
[
view.leadingAnchor.constraint(equalTo: leadingAnchor),
view.trailingAnchor.constraint(equalTo: trailingAnchor),
view.widthAnchor.constraint(equalToConstant: 100),
view.heightAnchor.constraint(equalToConstant: 100),
].forEach {$0.isActive = true}
The provided inline function(closure) {$0.isActive = true} will be called for each element of the array.
$0 here is a shorthand parameter name. In our case it holds reference to an array element, a NSConstraint object.

Getting nslayoutconstraints from UIView

after i have set up the constraints using anchor properties, such as this.
pageControl.anchorWithFixedHeight(nil, leading: view.leadingAnchor, bottom: view.bottomAnchor, trailing: view.trailingAnchor, topConstant: 0, leadingConstant: 0, bottomConstant: 0, trailingConstant: 0, heightConstant: 30)
where anchorWithFixedHeight is just a helper function like the following:
func anchorWithFixedHeight(_ top: NSLayoutYAxisAnchor? = nil, leading: NSLayoutXAxisAnchor? = nil, bottom: NSLayoutYAxisAnchor? = nil, trailing: NSLayoutXAxisAnchor? = nil, topConstant: CGFloat = 0, leadingConstant: CGFloat = 0, bottomConstant: CGFloat = 0, trailingConstant: CGFloat = 0, heightConstant: CGFloat = 0) -> [NSLayoutConstraint] {
translatesAutoresizingMaskIntoConstraints = false
if let top = top {
anchors.append(topAnchor.constraint(equalTo: top, constant: topConstant))
}
if let leading = leading {
anchors.append(leadingAnchor.constraint(equalTo: leading, constant: leadingConstant))
}
if let bottom = bottom {
anchors.append(bottomAnchor.constraint(equalTo: bottom, constant: bottomConstant))
}
if let trailing = trailing {
anchors.append(trailingAnchor.constraint(equalTo: trailing, constant: trailingConstant))
}
anchors.append(heightAnchor.constraint(equalToConstant: heightConstant))
}
Now, let's say i wish to get the bottom anchor constraint to change its property, how can i achieve that?
Thanks in advance
Edit: The anchors here is an array of NSlayoutContraint. My current method works in such a way that i append all the needed constraints into this array, then get whichever constraint, for example bottom anchor constraint from this array.
However, i felt that the solution is not eloquent enough. Thus, was wondering if there is any other better approach.
Let's clean up the signature of your method first. Since the constants are useless without the corresponding anchors, let's pass them together in tuples. We should also name the function after what it returns, which is constraints, not anchors.
If you want access to the constraints by what they constrain, we can do that in a type-safe way by returning a dictionary whose key type is NSLayoutAttribute and whose value type is NSLayoutConstraint. Thus we will call this new method like this:
let root = UIView()
let view = UIView()
root.addSubview(view)
let constraints = view.helperConstraints(
leading: (root.leadingAnchor, 20),
bottom: (root.bottomAnchor, 10),
trailing: (root.trailingAnchor, 20),
heightConstant: 30)
// Activate all the new constraints.
NSLayoutConstraint.activate(Array(constraints.values))
// Change the constant of the bottom constraint.
constraints[.bottom]!.constant = 40
The implementation isn't that different from yours:
extension UIView {
func helperConstraints(
top: (NSLayoutYAxisAnchor, CGFloat)? = nil,
leading: (NSLayoutXAxisAnchor, CGFloat)? = nil,
bottom: (NSLayoutYAxisAnchor, CGFloat)? = nil,
trailing: (NSLayoutXAxisAnchor, CGFloat)? = nil,
heightConstant: CGFloat? = nil) -> [NSLayoutAttribute: NSLayoutConstraint] {
translatesAutoresizingMaskIntoConstraints = false
var constraints = [NSLayoutAttribute: NSLayoutConstraint]()
if let (anchor, constant) = top {
constraints[.top] = topAnchor.constraint(equalTo: anchor, constant: constant)
}
if let (anchor, constant) = leading {
constraints[.leading] = leadingAnchor.constraint(equalTo: anchor, constant: constant)
}
if let (anchor, constant) = bottom {
constraints[.bottom] = bottomAnchor.constraint(equalTo: anchor, constant: constant)
}
if let (anchor, constant) = trailing {
constraints[.trailing] = trailingAnchor.constraint(equalTo: anchor, constant: constant)
}
if let constant = heightConstant {
constraints[.height] = heightAnchor.constraint(equalToConstant: constant)
}
return constraints
}
}

Resources