iOS - Position UIView in center of superview using Auto Layout programmatically - iphone

How do you programmatically set a UIView to be in the center of its superview using Auto Layout?
UIButton* viewObj = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[viewObj setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewObj setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:viewObj];
NSLayoutConstraint* cn = [NSLayoutConstraint constraintWithItem:viewObj
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0];
[self.view addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:viewObj
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterY
multiplier:1.0
constant:0];
[self.view addConstraint:cn];
The above code works for me for UIButton, but I'm having trouble replacing the first line with something that works for a UIView.
I've tried
UIView* viewObj = [UIView alloc] initWithFrame:CGRectZero];
but the view does not show up in simulator.
Any suggestions?
Thanks!

Since you asked this question in the context of using Auto Layout, the issue here is that a UIButton has an intrinsic size (communicated through the intrinsicContentSize method) that provides Auto Layout with information about width and height, but a UIView normally does not. So you need to add more constraints related to width and height.
If you want your UIView to be a set size (say, 200x200), you could add these lines:
cn = [NSLayoutConstraint constraintWithItem:viewObj
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:200];
[viewObj addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:viewObj
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:200];
[viewObj addConstraint: cn];
Note that the toItem: argument is nil and the second attribute is NSLayoutAttributeNotAnAttribute, because you aren't specifying the width and height relative to anything else. If you want the subview's height and width to be relative to the superview (say, 0.5), you could do this:
cn = [NSLayoutConstraint constraintWithItem:viewObj
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
[self.view addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:viewObj
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:0.5
constant:0];
[self.view addConstraint: cn];

UIButton, UIImageView, UIlabel and UITextField can automatically set their size according to their content properties. The width and height of a UIImageView are set by the UIImage it may contain. The size of a UILabel will depend on its text. The width and height of a UIButton are defined by the title and the image it has (you can learn more about it with Intrinsic Content Size).
Therefore, when you want to center a UIButton, a UILabel, a UITextField or a UIImageView inside a UIView with auto layout, in almost all cases you don't have to create constraints for their width and height. You simply need to set horizontal and vertical constraints for them.
However, with auto layout, a UIView that has no subviews can't rely on anything to set its size unless you provide it some arbitrary width and height constraints. And according to your needs, you can solve this in 3 different ways.
The pure auto layout style
Here, we set the width and height of our UIView directly as auto layout constraints:
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView()
newView.backgroundColor = .redColor()
newView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(newView)
// Auto layout code using anchors (iOS9+)
let horizontalConstraint = newView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor)
let verticalConstraint = newView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
let widthConstraint = newView.widthAnchor.constraintEqualToAnchor(nil, constant: 100)
let heightConstraint = newView.heightAnchor.constraintEqualToAnchor(nil, constant: 100)
NSLayoutConstraint.activateConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
The autoresizing mask style
Here, we initialize our UIView with its width and height, make its center and its superview's center equal and create some autoresizing masks. Then, we ask UIKit to translate those autoresizing masks into auto layout constraints:
override func viewDidLoad() {
super.viewDidLoad()
let newView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
newView.backgroundColor = .redColor()
newView.center = CGPointMake(view.bounds.midX, view.bounds.midY)
newView.autoresizingMask = [.FlexibleLeftMargin, .FlexibleRightMargin, .FlexibleTopMargin, .FlexibleBottomMargin]
newView.translatesAutoresizingMaskIntoConstraints = true // default is true
view.addSubview(newView)
}
The subclass style
Here, we create a subclass of UIView and override its intrinsicContentSize() method (declaration) so that it returns the desired size:
import UIKit
class CustomView: UIView {
override func intrinsicContentSize() -> CGSize {
return CGSize(width: 100, height: 100)
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let newView = CustomView()
newView.backgroundColor = .redColor()
newView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(newView)
let horizontalConstraint = NSLayoutConstraint(item: newView,
attribute: .CenterX,
relatedBy: .Equal,
toItem: view,
attribute: .CenterX,
multiplier: 1,
constant: 0)
view.addConstraint(horizontalConstraint)
let verticalConstraint = NSLayoutConstraint(item: newView,
attribute: .CenterY,
relatedBy: .Equal,
toItem: view,
attribute: .CenterY,
multiplier: 1,
constant: 0)
view.addConstraint(verticalConstraint)
}
}

Related

UIScrollView content inset defined by a height of a view outside of the subtree

Given a view hierarchy:
- View `container`
- UIScrollView `scrollView`
- UIView `content`
- UIView `footer`
I would like the UIScrollView.contentInset.bottom to be equal to footer.bounds.height.
Question: Can this be expressed using Auto Layout?
Now, there is a very evident brute-force approach that I am aware of and that works:
Observe changes to the bounds property of the footer
scrollView.contentInset.bottom = -footer.bounds.height once footer's parent has finished layoutSubviews().
Or alternatively I could have a constraint between content.bottom and scrollView.bottom (which, as, I'm sure, you are aware, indicates the bottom content inset for non-ambiguously size content) and have its constant altered each time the footer bounds change.
But the point is that all of those approaches are very on-the-nose, really makes me uncomfortable for the terrible code they produce so I am wondering:
Can this be expressed using Auto Layout?
I have attempted to do the following:
content.bottomAnchor.constraint(equalTo: footer.topAnchor)
Hoping that content.bottomAnchor would be treated as the bottom inset of the scroll view content, but nope - Auto Layout literally treats it as me constraining content's bottom to the footer's top.
OK - one approach...
As of iOS 11 (I'm assuming you don't need to target earlier than that), a subview of a UIScrollView can be constrained to the scroll view's Frame Layout Guide. This made it easy to add non-scrolling UI elements to the scroll view hierarchy.
Based on this hierarchy:
- view
- scrollView
- contentView
- element1
- element2
- element3
- UILayoutGuide
- footerView
What we'll do is:
add all the "scrollable" elements to the contentView
plus add a UILayoutGuide to the contentView which will serve as or "bottom" scrollable element
add the footerView to the scrollView last so it is at the top of the z-order
constrain the footerView to the scrollView's Frame Layout Guide so it stays put
constrain the heightAnchor of our UILayoutGuide equal to the heightAnchor of the footerView
Because a UILayoutGuide is a non-rendering view, it will not be visible but it will create the space from the bottom of our last viewable element to the bottom of the contentView -- and it will automatically change height if/when the footerView changes height.
Here's a complete example - scrollView / contentView / 3 imageViews / layout guide / translucent footerView:
class ExampleViewController: UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.backgroundColor = .lightGray
return v
}()
let contentView: UIView = {
let v = UIView()
v.backgroundColor = .cyan
return v
}()
let footerView: UILabel = {
let v = UILabel()
v.textAlignment = .center
v.textColor = .white
v.font = UIFont.systemFont(ofSize: 24.0, weight: .bold)
v.text = "Footer View"
v.backgroundColor = UIColor.black.withAlphaComponent(0.65)
return v
}()
var imgView1: UIImageView = {
let v = UIImageView()
v.backgroundColor = .red
v.image = UIImage(systemName: "1.circle")
v.tintColor = .white
return v
}()
var imgView2: UIImageView = {
let v = UIImageView()
v.backgroundColor = .green
v.image = UIImage(systemName: "2.circle")
v.tintColor = .white
return v
}()
var imgView3: UIImageView = {
let v = UIImageView()
v.backgroundColor = .blue
v.image = UIImage(systemName: "3.circle")
v.tintColor = .white
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
// add 3 image views as the content we want to see
contentView.addSubview(imgView1)
contentView.addSubview(imgView2)
contentView.addSubview(imgView3)
// add contentView to srollView
scrollView.addSubview(contentView)
// add footer view to scrollView last so it's at the top of the z-order
scrollView.addSubview(footerView)
view.addSubview(scrollView)
[scrollView, contentView, footerView, imgView1, imgView2, imgView3].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
}
// "spacer" for bottom of scroll content
// we'll constrain it to the height of the footer view
let spacerGuide = UILayoutGuide()
contentView.addLayoutGuide(spacerGuide)
let g = view.safeAreaLayoutGuide
let svCLG = scrollView.contentLayoutGuide
let scFLG = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
// constrain scrollView view 40-pts on all 4 sides to view (safe-area)
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
scrollView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
// contentView view 0-pts top / leading / trailing / bottom to scrollView contentLayoutGuide
contentView.topAnchor.constraint(equalTo: svCLG.topAnchor, constant: 0.0),
contentView.leadingAnchor.constraint(equalTo: svCLG.leadingAnchor, constant: 0.0),
contentView.trailingAnchor.constraint(equalTo: svCLG.trailingAnchor, constant: 0.0),
contentView.bottomAnchor.constraint(equalTo: svCLG.bottomAnchor, constant: 0.0),
// contentView width == scrollView frameLayoutGuide width
contentView.widthAnchor.constraint(equalTo: scFLG.widthAnchor, constant: 0.0),
// imgView1 to top of contentView
imgView1.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0.0),
// imgView1 width / height
imgView1.widthAnchor.constraint(equalToConstant: 240.0),
imgView1.heightAnchor.constraint(equalToConstant: 240.0),
// imgView1 centerX to contentView centerX
imgView1.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// imgView2 top to bottom of imgView1 + 20-pt spacing
imgView2.topAnchor.constraint(equalTo: imgView1.bottomAnchor, constant: 20.0),
// imgView2 width / height
imgView2.widthAnchor.constraint(equalToConstant: 200.0),
imgView2.heightAnchor.constraint(equalToConstant: 280.0),
// imgView2 centerX to contentView centerX
imgView2.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// imgView3 top to bottom of imgView2 + 20-pt spacing
imgView3.topAnchor.constraint(equalTo: imgView2.bottomAnchor, constant: 20.0),
// imgView3 width / height
imgView3.widthAnchor.constraint(equalToConstant: 280.0),
imgView3.heightAnchor.constraint(equalToConstant: 320.0),
// imgView3 centerX to contentView centerX
imgView3.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
// spacerGuide top to bottom of actual content
// spacerGuide top to imgView3 bottom
spacerGuide.topAnchor.constraint(equalTo: imgView3.bottomAnchor, constant: 0.0),
// spacerGuide to leading / trailing / bottom of contentView
spacerGuide.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0.0),
spacerGuide.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0.0),
spacerGuide.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0.0),
// footerView to leading / trailing / bottom of scrollView frameLayoutGuide
// (constrained to frameLayoutGuide so it won't scroll)
footerView.leadingAnchor.constraint(equalTo: scFLG.leadingAnchor, constant: 0.0),
footerView.trailingAnchor.constraint(equalTo: scFLG.trailingAnchor, constant: 0.0),
footerView.bottomAnchor.constraint(equalTo: scFLG.bottomAnchor, constant: 0.0),
// footerView height == scrollView height with 0.25 multiplier
// (so it will change height when scrollView changes height, such as device rotation)
footerView.heightAnchor.constraint(equalTo: scFLG.heightAnchor, multiplier: 0.25),
// finally, spacerGuide height equal to footerView height
spacerGuide.heightAnchor.constraint(equalTo: footerView.heightAnchor),
])
}
}
Result:
Scrolled to the bottom:
and rotated (so we see the footerView height change) scrolled all the way to the bottom:
Edit
The answer to the specific question is: you can't.
A scroll view's contentInset is not an object to which you can add constraints... it's a Property of the scroll view. Much like you could not constrain a scroll view's .backgroundColor to an auto-layout constraint.
Landed here after looking for a solution, in my case the scroll view is actually a UICollectionView, so adding "helper" elements to the layout (as suggested by another answer) would have been more challenging (changing dataSource logic etc)
I ended up doing the following:
(This example assumes you have a bottomView attached to the bottom of the screen and you want your scrollView / collectionView to be inset based on it)
Set scrollView.contentInsetAdjustmentBehavior = .never
Then in your View Controller, do this:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.scrollView.contentInset = .init(top: self.view.safeAreaLayoutGuide.layoutFrame.minY,
left: 0,
bottom: bottomView.frame.height,
right: 0)
}
Note that if you also want to account for left and right safe area (e.g. landscape avoiding iPhone notch), you can do:
[...]
left: self.view.safeAreaLayoutGuide.layoutFrame.minX
[...]
right: self.view.frame.width - self.view.safeAreaLayoutGuide.layoutFrame.maxX

Scrollview without NavigationController

I have no idea why I cannot add a working scroll view without embedding the VC in a navigation controller.
Here is my code for a VC which I open from a tab bar controller and it's not embedded in a navigation controller:
lazy var contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height)
lazy var scrollView : UIScrollView = {
let scrollView = UIScrollView(frame: view.bounds)
scrollView.backgroundColor = .white
scrollView.frame = self.view.bounds
scrollView.contentSize = contentSize
scrollView.autoresizingMask = UIView.AutoresizingMask.flexibleHeight
scrollView.bounces = true
return scrollView
}()
lazy var containerView : UIView = {
let view = UIView()
view.backgroundColor = .white
view.frame.size = contentSize
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
setupElements()
}
func setupElements() {
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(containerView)
let stackView = UIStackView()
containerView.addSubview(stackView)
stackView.axis = .vertical
stackView.distribution = .fillEqually
stackView.spacing = 12
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.topAnchor, constant: 60).isActive = true
stackView.leadingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.leadingAnchor, constant: 20).isActive = true
stackView.trailingAnchor.constraint(equalTo: containerView.safeAreaLayoutGuide.trailingAnchor, constant: -20).isActive = true
}
I have a bunch of textfields and buttons in the stackview and they show up fine but the view does not scroll (vertically). What am I doing wrong?
You need to calculate the content size
Ex.
scrollView.contentSize = CGSize(width: self.view.frame.width, height: self.view.frame.height + 100)
Also, try to consolidate your layout. Try using Autolayout
Your scrollView Content size should be bigger than your scrollView frame to make it scroll
scrollView.contentSize = contentSize

Trouble getting UIImageView inside ScrollView programmatically

After days of trying, and searching through countless SO/google/YouTube pages, I unclear how to accomplish this: I'm trying to place a single tall, narrow image inside a UIScrollView that only takes up a section of the screen, only scrolls vertically, and is preferably only coded programmatically, no Interface Builder at all.
I've managed to create the scrollView, set the backgroundColor to blue so I can see it and managed to use constraint anchors to pin it exactly where I need it to be. I then added the top and bottom labels as every video tutorial was telling me to, but I've since deleted these as they didn't seem necessary once I added the image.
The problems start as soon as I try to add the image. I've added an example image below as it's a tall, narrow image.
https://imgur.com/7qI1IaT
If you run the code with the image, you'll see:
The image scrolls horizontally as well as vertically. I'd have thought content.didOffset.x < 0 would work, but apparently not. There's probably a simple method to fix this but I'm yet to find it.
If the height of the image is less than the height of the scrollView, i want the image to stretch to fit the scrollView. I used both .scaleAspectFit and .scaleAspectFill and neither of these seemed to change anything.
The width of the image (or at least, the image I'm using, not the example image) is larger than the section of scrollView I have, and it goes off the screen. Again, I'm sure there's an easy fix to this, but I don't know.
Here is my code, but it's probably all wrong.
import UIKit
class ViewController: UIViewController {
lazy var scrollView: UIScrollView = {
let view = UIScrollView()
view.translatesAutoresizingMaskIntoConstraints = false
view.frame.size.height = 3000
view.backgroundColor = UIColor.blue
return view
}()
let imageView: UIImageView = {
let image = UIImageView(image: imageLiteral)
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
func setupLayout() {
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100).isActive = true
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 100).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
scrollView.addSubview(imageView)
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
imageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor).isActive = true
imageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor).isActive = true
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
imageView.frame.size.height = scrollView.frame.size.height
imageView.frame.size.width = scrollView.frame.size.width
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(scrollView)
setupLayout()
}
}
I don't know if I'm doing the right thing by adding the image as a subview of scrollView. I couldn't get the image to scroll at all until I changed it from the subview of view to scrollView. The labels in the tutorials I've seen were added that way, and it made more sense to me to add it into the scrollView than the main screen view, but again, this could be wrong.
I'm really not sure if it's the constraints, the contentSize or what, but it’s pretty clear I don't know what I’m doing, and I don't want to just wing it, so if anyone knows of any YouTube videos or websites that can help me out, I’d really appreciate it.
Again, apologies. I feel like this is a really simple fix, but I just don't have it.
There are a number of ways of accomplishing this, but I’d be inclined to set the zoomScale of the scroll view appropriate for this image view width, e.g.
// we want to make sure we adjust scale as views are laid out
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if imageView.intrinsicContentSize.width != 0 {
let scale = scrollView.bounds.width / imageView.intrinsicContentSize.width
scrollView.maximumZoomScale = scale
scrollView.minimumZoomScale = scale
scrollView.zoomScale = scale
}
}
To do that, you’ll have to set the delegate of the UIScrollView:
scrollView.delegate = self // we need to specify delegate so we can implement `viewForZooming(in:)`
And implement viewForZooming(in:):
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
So pulling that all together:
class ViewController: UIViewController {
let sampleImage: UIImage = ...
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
// view.frame.size.height = 3000 // not needed as we're using constraints
scrollView.backgroundColor = .blue
return scrollView
}()
let imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
// imageView.contentMode = .scaleAspectFit // not needed as we're going to let the intrinsic size dictate the size of the image view and therefore no scaling is happening
imageView.clipsToBounds = true
return imageView
}()
func setupLayout() {
view.addSubview(scrollView)
scrollView.addSubview(imageView)
imageView.image = sampleImage
scrollView.delegate = self // we need to specify delegate so we can implement `viewForZooming(in:)`
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 200),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 100),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
imageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor)
])
// these are not needed because we're using constraints
//
// imageView.frame.size.height = scrollView.frame.size.height
// imageView.frame.size.width = scrollView.frame.size.width
}
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
}
// we want to make sure we adjust scale as views are laid out
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if imageView.intrinsicContentSize.width != 0 {
let scale = scrollView.bounds.width / imageView.intrinsicContentSize.width
scrollView.maximumZoomScale = scale
scrollView.minimumZoomScale = scale
scrollView.zoomScale = scale
}
}
}
extension ViewController: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return imageView
}
}
You need to constraint the image width to the scrollview width. However you cannot do it directly, because the image is a subview of the scrollview and direct constraint would refer to width of the content of the scrollview not width. I have solved it by adding a layout guide that is constrained to the width of the scrollview "from the outside".
Also when you add constraint for the width you are left with intrinsic constraint for the height and that would change aspect ratio of the image. You need to add a constraint for the original aspect ratio.
Here is my code:
class ViewController: UIViewController {
let scrollView = UIScrollView()
let imageView = UIImageView(image: UIImage(named: "tallimage"))
let widthGuide = UILayoutGuide()
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupLayout()
}
func setupViews() {
scrollView.backgroundColor = UIColor.blue
view.addSubview(scrollView)
scrollView.addSubview(imageView)
view.addLayoutGuide(widthGuide)
}
func setupLayout() {
let ratio: CGFloat = (imageView.image?.size.height ?? 1) / (imageView.image?.size.width ?? 1)
imageView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 100),
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -10),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
imageView.topAnchor.constraint(equalTo: scrollView.topAnchor),
imageView.leftAnchor.constraint(equalTo: scrollView.leftAnchor),
imageView.rightAnchor.constraint(equalTo: scrollView.rightAnchor),
imageView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
// Make the image the same width as the scrollview.
widthGuide.widthAnchor.constraint(equalTo: scrollView.widthAnchor),
imageView.widthAnchor.constraint(equalTo: widthGuide.widthAnchor),
// Keep the height/width ratio of the image so it is not deformed.
imageView.heightAnchor.constraint(equalTo: imageView.widthAnchor, multiplier: ratio),
])
}
}
I have also changed style of the code. Feel free to use your original style.
However, I like to
When I call a method, it is defined below the line where it is used (in order).
Use NSLayoutConstraint.activate() when activating more constraints.
Use simple instance variables (let constants) and configure them later.

How to add the vertical alignment between Button and label programmatically

Rather creating a Xib file and loading it into tableview. I am creating a label and button in Header view.
var btnTimeZone = UIButton(type: .Custom)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.btnTimeZone.titleLabel?.font = UIFont.boldSystemFontOfSize(14.0)
self.btnTimeZone.backgroundColor = UIColor.clearColor()
self.btnTimeZone.setTitleColor(UIColor.blackColor(), forState: .Normal)
self.btnTimeZone.addTarget(self, action: #selector(self.selectClicked), forControlEvents: UIControlEvents.TouchUpInside)
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 150))
headerView.layer.borderWidth = 2
headerView.layer.borderColor = UIColor.whiteColor().CGColor
headerView.backgroundColor = ClientConfiguration.primaryUIColor()
let myLabel = UILabel()
myLabel.frame = CGRectMake(0, 0, tableView.frame.width - 70, 30)
myLabel.font = UIFont.boldSystemFontOfSize(10)
myLabel.backgroundColor = ClientConfiguration.primaryUIColor()
myLabel.textColor = UIColor.whiteColor()
myLabel.textAlignment = .Left
myLabel.text = "please Select your Time Zone"
let frame = CGRectMake(0, 0,headerView.frame.width , 50)
self.btnTimeZone.frame = frame
headerView.addSubview(myLabel)
headerView.addSubview(self.btnTimeZone)
return headerView
}
I want label above button in header view but I am not able to this..??
how can I do this..??
You can use something like the following to add a layout constraint via code:
let top = NSLayoutConstraint(item:myLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem:headerView, attribute: NSLayoutAttribute.top, multiplier:1.0, constant:0)
headerView.addConstraint(top)
Of course the issue would be that one constraint alone might not be enough and you'd need to add others to control the spacing between the label and the button and to also control the horizontal spacing for the elements.

Different background colors for the top and bottom of a UITableView

If you look at your Inbox in iPhone OS 3.0's Mail app, you'll see that swiping down displays a grayish background color above the UISearchBar.
Now, if you scroll down to the bottom of the table, you'll see that the background color at that end is white.
I can think of a couple ways of solving this problem, but they're pretty hacky:
Change the table view's background color depending on the current scrollOffset by overriding -scrollViewDidScroll:
Give the UITableView a clear background color and then set its superview's backgroundColor to a gradient pattern image.
Does anyone know what the "best practice" solution is for this problem? thanks.
There´s good answers at Light gray background in “bounce area”...
Where i found this codesnipet (slightly modified) that works great:
CGRect frame = self.tableView.bounds;
frame.origin.y = -frame.size.height;
UIView* grayView = [[UIView alloc] initWithFrame:frame];
grayView.backgroundColor = [UIColor grayColor];
[self.tableView addSubview:grayView];
[grayView release];
Swift:
var frame = self.tableView.bounds
frame.origin.y = -frame.size.height
let grayView = UIView(frame: frame)
grayView.backgroundColor = .gray
self.tableView.addSubview(grayView)
Swift 5.0+
Solution with an extension:
extension UITableView {
func addTopBounceAreaView(color: UIColor = .white) {
var frame = UIScreen.main.bounds
frame.origin.y = -frame.size.height
let view = UIView(frame: frame)
view.backgroundColor = color
self.addSubview(view)
}
}
Usage: tableView.addTopBounceAreaView()
The easiest and most lightweight way to solve this problem is:
Set the background color of the table view to whatever you want - in your case, white.
Put the search bar view inside a container view. Set the table view's header view to this container view (instead of the search bar view itself, which is probably what you were doing previously).
In that container view, add another subview with frame equal to a rect like (0, -480, 320, 480), and set the background color of that subview to whatever color you want - in your case, grayish.
That should be all you need to do. I just did this myself and achieved the look I wanted, exactly the same as the Mail app. Using scrollViewDidScroll is a major waste of CPU resources, and subclassing UITableView is super messy, IMO.
Set the tableFooterView to a view of 0 height and width that draws way outside its bounds. An easy way is to add a big subview to it:
self.tableView.tableFooterView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
UIView *bigFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 1000)];
bigFooterView.backgroundColor = [UIColor whiteColor];
bigFooterView.opaque = YES;
[self.tableView.tableFooterView addSubview:bigFooterView];
[bigFooterView release];
adjust [UIColor whiteColor] and the width of your bigFooterView accordingly (if your tableView can go horizontal, you'll want it to be wider than 320). This way at the top you will see whatever your table view background is, and on the bottom whatever you set this view's background to.
Courtesy of Erica Sadun:
- (void) scrollViewDidScroll: (UIScrollView *) sv
{
float percent = sv.contentOffset.y / sv.contentSize.height;
percent = 0.5 + (MAX(MIN(1.0f, percent), 0.0f) / 2.0f);
sv.backgroundColor = [UIColor colorWithRed:percent * 0.20392
green:percent * 0.19607
blue:percent * 0.61176 alpha: 1.0f];
}
and then here's the modified version I'm using:
- (void)scrollViewDidScroll:(UIScrollView *)sv
{
UIColor *backgroundColor = nil;
float percent = sv.contentOffset.y / sv.contentSize.height;
percent = 0.5 + (MAX(MIN(1.0f, percent), 0.0f) / 2.0f);
if (0.5f == percent)
{
backgroundColor = RGBCOLOR(233.0f, 235.0f, 237.0f);
}
else
{
CGFloat r = 233.0f * (1.0f - percent) + 255.0f * percent;
CGFloat g = 235.0f * (1.0f - percent) + 255.0f * percent;
CGFloat b = 237.0f * (1.0f - percent) + 255.0f * percent;
backgroundColor = RGBCOLOR(r,g,b);
}
sv.backgroundColor = backgroundColor;
}
Here is the Swift 3 version:
var frame = self.tableView.bounds
frame.origin.y = -frame.size.height
let view = UIView(frame: frame)
view.backgroundColor = .gray
self.tableView.addSubview(view)
This might not be a "best practice," but if you really want to do it like Apple, there's a private UITableView property called tableHeaderBackgroundColor. The grayish color is #e2e7ed.
You could put something like this in the -viewDidLoad method of a UITableViewController:
UIColor *grayishColor = [UIColor colorWithRed:226/255.0
green:231/255.0
blue:237/255.0 alpha:1.0];
[self.tableView setValue:grayishColor forKey:#"tableHeaderBackgroundColor"];
I solved this problem with the use of autolayouts. The solution works on different screen sizes and with orientation change.
self.tableView.tableFooterView = UIView();
if let tableFooterView = self.tableView.tableFooterView {
let bigFooterView = UIView();
bigFooterView.backgroundColor = UIColor.white;
bigFooterView.isOpaque = true;
tableFooterView.addSubview(bigFooterView);
bigFooterView.translatesAutoresizingMaskIntoConstraints = false;
tableFooterView.addConstraint(NSLayoutConstraint(item: bigFooterView, attribute: .trailing, relatedBy: .equal, toItem: tableFooterView, attribute: .trailing, multiplier: 1, constant: 0));
tableFooterView.addConstraint(NSLayoutConstraint(item: bigFooterView, attribute: .leading, relatedBy: .equal, toItem: tableFooterView, attribute: .leading, multiplier: 1, constant: 0));
tableFooterView.addConstraint(NSLayoutConstraint(item: bigFooterView, attribute: .top, relatedBy: .equal, toItem: tableFooterView, attribute: .top, multiplier: 1, constant: 0));
tableFooterView.addConstraint(NSLayoutConstraint(item: bigFooterView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 1000));
}
I have expanded the answer in Light gray background in “bounce area” of a UITableView to the bottom side as well. Hope this helps :)
CGRect topFrame = self.tableView.bounds;
topFrame.origin.y = -topFrame.size.height;
UIView* topView = [[UIView alloc] initWithFrame:topFrame];
topView.backgroundColor = [UIColor grayColor]; // change to any color you want
[self.tableView addSubview:topView];
CGRect bottomFrame = self.tableView.bounds;
bottomFrame.origin.y = self.tableView.contentSize.height;
UIView* bottomView = [[UIView alloc] initWithFrame:bottomFrame];
bottomView.backgroundColor = [UIColor grayColor]; // change to any color you want
[self.tableView addSubview:bottomView];
This is my solution:
let topColor = UIColor.blue
let bottomColor = UIColor.black
self.tableView.backgroundColor = topColor
self.tableView.tableFooterView = UIView(frame: CGRect.zero)
let footerView = UIView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 1000))
footerView.backgroundColor = bottomColor
self.tableView.tableFooterView?.addSubview(footerView)
SwiftUI solution
var body: some View {
NavigationView {
List(data, id: \.self) { data in
Text("\(data)")
}
.onAppear {
let headerView = UIView(frame: CGRect(x: 0, y: -400, width: UIScreen.main.bounds.width, height: 400.0))
headerView.backgroundColor = .lightGray
UITableView.appearance().addSubview(headerView)
}
.navigationBarTitle("Title", displayMode: .inline)
}
}
If you want a different background color below the List then add another UIView to change the backgroundView:
let backgroundView = UIView()
backgroundView.backgroundColor = .black
UITableView.appearance().backgroundView = backgroundView
You should look into using the tableHeaderView and tableFooterView properties of the UITableView.
I think you just want to set your cell's BG Color to white, and make the table's BG color the other (gray) color. Im not sure you'd have success trying to do that with transparent cells.