Issue with UIImageView background in rotation - ios

Hello
I'm trying to put an Image as a background for my ViewControllers, guiding myself for other posts I found this way:
I created the following extension:
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "msa_background")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
imageViewBackground.clipsToBounds = true
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}
}
I'm calling this extension in every ViewController with the following code:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
}
The problem is when I rotated the screen the image in the background don't fill all the space in the ViewController, I have checked any possible solution that I found but I can't find a way to do it.
I really appreciate any help from you

You are adding the image with the current frame of the screen and never changing it. When you rotate the device it will keep the same frame.
Change it to use AutoLayout like this...
extension UIView {
func addBackground(imageName: String, contentMode: UIViewContentMode) {
let imageViewBackground = UIImageView()
imageViewBackground.image = UIImage(named: imageName)
// you can change the content mode:
imageViewBackground.contentMode = contentMode
imageViewBackground.clipsToBounds = true
imageViewBackground.translatesAutoresizingMaskIntoConstraints = false
self.insertSubview(imageViewBackground, atIndex: 0)
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[imageViewBackground]|",
options: [],
metrics: nil,
views: ["imageViewBackground", imageViewBackground]))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[imageViewBackground]|",
options: [],
metrics: nil,
views: ["imageViewBackground": imageViewBackground]))
}
}
The auto layout constraints will then make the image view fit to the view no matter how the orientation of the device or frame of the view changes.

Related

Give inset to UIImageView before setting Its frame

I have an UITextField component which has an icon on the right side of it. Icon is just an UIImageView. I'm trying to give inset to UIImageView with below code
imageView.image = UIImage(named: "example")?.withAlignmentRectInsets(UIEdgeInsets(top: -4, left: 0, bottom: -4, right: 0))
However, It doesn't work cuz It's frame hasn't been set when setting Its' Image. I'm giving Its frame with below code;
iconImageView.frame = CGRect(
x: bounds.size.width - iconWidth - iconMarginLeft,
y: bounds.size.height - textHeight() - iconMarginBottom,
width: iconWidth,
height: textHeight()
)
and how I setup UIImageView
fileprivate func createIconImageView() {
let iconImageView = UIImageView()
iconImageView.backgroundColor = .clear
iconImageView.contentMode = .scaleAspectFill
iconImageView.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
if (hasToolTip) {
let tapGesture = UITapGestureRecognizer(target: self, action:#selector(toolTipTapped(_:)))
iconImageView.addGestureRecognizer(tapGesture)
iconImageView.isUserInteractionEnabled = true
let image = UIImage(named: "infotooltip")
image?.accessibilityIdentifier = "tooltipImage"
iconImageView.image = image!
changeIconAlphaToOne()
self.iconImageView = iconImageView
} else {
self.iconImageView = iconImageView
changeIconAlphaToZero()
}
addSubview(iconImageView)
}
My problem is that, I need to give UIEdgeInset to the image. However, because I'm trying to give it before setting the frame, It doesn't work. How can I achieve this?
I also try to resize the image by referencing below post but doesn't work for me. I also DO NOT want to put it inside a content view which will make my component class more complicated.
The simplest way to resize an UIImage?

Can I make a subview fill the view in its size?

I have a view, say a red rectangle, and I have a subview of it, a green rectangle, all added programmatically.
Now, how can I make a green rectangle fill the whole red rectangle in width and height without getting current size of the screen, so that when red rectangle resizes on screen rotation, the green one resizes automatically too?
let redView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 350))
redView.backgroundColor = UIColor.redColor()
redView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
view.addSubview(redView)
let greenView = UIView(frame: redView.bounds)
greenView.backgroundColor = UIColor.greenColor()
redView.addSubview(greenView)
greenView.autoresizingMask = redView.autoresizingMask
class RedRect:UIView {
void layoutSubviews(){
greenRect.frame = bounds
}
}
should be frame = bounds
You can use Visual Format Auto Layout like below where self == YOUR_GREEN_RECT
guard let superview = self.superview else {
return
}
self.translatesAutoresizingMaskIntoConstraints = false
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
superview.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-0-[subview]-0-|", options: .DirectionLeadingToTrailing, metrics: nil, views: ["subview": self]))
Or just using Autoresizingmask
self.autoresizingMask = [.flexibleWidth, .flexibleHeight]
You can override the layoutSubviews() method of Red Rectangle. Inside the layout, just reassign the red rectangle frame to the green Rectangle frame. This would make the frame resize and refill on orientation as well.
This is merely an example code for reference.
class RedRect:UIView {
void layoutSubviews(){
greenRect.frame = bounds
}
}

Swift / cut background image

My background image is set with UIView extension.
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "index_clear")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}}
For segue animation the view moves from left to right. The background image is way larger than the screen, what is visible while the animation.
How can I cut the background image to perfectly fit into the view?
.ScaleAspectFit does the job, but since it's a picture it looks bad.
Help is very appreciated.
You can use .ScaleAspectFill to crop the image rather than scale it to fit. This won't distort your image, but obviously you won't be able to see the whole thing.
You need to ensure that you set clipsToBounds=true on the UIImageView so that the cropped area isn't visible
extension UIView {
func addBackground() {
// screen width and height:
let width = UIScreen.mainScreen().bounds.size.width
let height = UIScreen.mainScreen().bounds.size.height
let imageViewBackground = UIImageView(frame: CGRectMake(0, 0, width, height))
imageViewBackground.image = UIImage(named: "index_clear")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
imageViewBackground.clipsToBounds=true
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}

Setting tableHeaderView height dynamically

My application creates a UITableViewController that contains a custom tableHeaderView which may have an arbitrary height. I've been struggling with a way to set this header dynamically, as it seems the suggested ways have been cutting this header short.
My UITableViewController's relevant code:
import UIKit
import SafariServices
class RedditPostViewController: UITableViewController, NetworkCommunication, SubViewLaunchLinkManager {
//MARK: UITableViewDataSource
var post: PostData?
var tree: CommentTree?
weak var session: Session! = Session.sharedInstance
override func viewDidLoad() {
super.viewDidLoad()
// Get post info from api
guard let postData = post else { return }
//Configure comment table
self.tableView.registerClass(RedditPostCommentTableViewCell.self, forCellReuseIdentifier: "CommentCell")
let tableHeader = PostView(withPost: postData, inViewController: self)
let size = tableHeader.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize)
let height = size.height
let width = size.width
tableHeader.frame = CGRectMake(0, 0, width, height)
self.tableView.tableHeaderView = tableHeader
session.getRedditPost(postData) { (post) in
self.post = post?.post
self.tree = post?.comments
self.tableView.reloadData()
}
}
}
This results in the following incorrect layout:
If I change the line: tableHeader.frame = CGRectMake(0, 0, width, height) to tableHeader.frame = CGRectMake(0, 0, width, 1000) the tableHeaderView will lay itself out correctly:
I'm not sure what I'm doing incorrectly here. Also, custom UIView class, if this helps:
import UIKit
import Foundation
protocol SubViewLaunchLinkManager: class {
func launchLink(sender: UIButton)
}
class PostView: UIView {
var body: UILabel?
var post: PostData?
var domain: UILabel?
var author: UILabel?
var selfText: UILabel?
var numComments: UILabel?
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented yet")
}
init(withPost post: PostData, inViewController viewController: SubViewLaunchLinkManager) {
super.init(frame: CGRectZero)
self.post = post
self.backgroundColor = UIColor.lightGrayColor()
let launchLink = UIButton()
launchLink.setImage(UIImage(named: "circle-user-7"), forState: .Normal)
launchLink.addTarget(viewController, action: "launchLink:", forControlEvents: .TouchUpInside)
self.addSubview(launchLink)
selfText = UILabel()
selfText?.backgroundColor = UIColor.whiteColor()
selfText?.numberOfLines = 0
selfText?.lineBreakMode = .ByWordWrapping
selfText!.text = post.selfText
self.addSubview(selfText!)
selfText?.sizeToFit()
//let attributedString = NSAttributedString(string: "Test"/*post.selfTextHtml*/, attributes: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType])
//selfText.attributedText = attributedString
body = UILabel()
body!.text = post.title
body!.numberOfLines = 0
body!.lineBreakMode = .ByWordWrapping
body!.textAlignment = .Justified
self.addSubview(body!)
domain = UILabel()
domain!.text = post.domain
self.addSubview(domain!)
author = UILabel()
author!.text = post.author
self.addSubview(author!)
numComments = UILabel()
numComments!.text = "\(post.numComments)"
self.addSubview(numComments!)
body!.translatesAutoresizingMaskIntoConstraints = false
domain!.translatesAutoresizingMaskIntoConstraints = false
author!.translatesAutoresizingMaskIntoConstraints = false
selfText!.translatesAutoresizingMaskIntoConstraints = false
launchLink.translatesAutoresizingMaskIntoConstraints = false
numComments!.translatesAutoresizingMaskIntoConstraints = false
let views: [String: UIView] = ["body": body!, "domain": domain!, "author": author!, "numComments": numComments!, "launchLink": launchLink, "selfText": selfText!]
//let selfTextSize = selfText?.sizeThatFits((selfText?.frame.size)!)
//print(selfTextSize)
//let metrics = ["selfTextHeight": selfTextSize!.height]
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[domain]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[author]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[body]-[selfText]-[numComments]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[launchLink]-[numComments]-|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[body][launchLink]|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[selfText][launchLink]|", options: [], metrics: nil, views: views))
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[domain][author][numComments][launchLink]|", options: [], metrics: nil, views: views))
}
override func layoutSubviews() {
super.layoutSubviews()
body?.preferredMaxLayoutWidth = body!.bounds.width
}
}
Copied from this post. (Make sure you see it if you're looking for more details)
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = tableView.tableHeaderView {
let height = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height
var headerFrame = headerView.frame
//Comparison necessary to avoid infinite loop
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
}
}
}
Determining the header's frame size using
header.systemLayoutSizeFitting(UILayoutFittingCompressedSize)
as suggested in the answers above didn't work for me when my header view consisted of a single multiline label. With the label's line break mode set to wrap, the text just gets cut off:
Instead, what did work for me was using the width of the table view and a height of 0 as the target size:
header.systemLayoutSizeFitting(CGSize(width: tableView.bounds.width, height: 0))
Putting it all together (I prefer to use an extension):
extension UITableView {
func updateHeaderViewHeight() {
if let header = self.tableHeaderView {
let newSize = header.systemLayoutSizeFitting(CGSize(width: self.bounds.width, height: 0))
header.frame.size.height = newSize.height
}
}
}
And call it like so:
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
tableView.updateHeaderViewHeight()
}
More condensed version of OP's answer, with the benefit of allowing layout to happen naturally (note this solution uses viewWillLayoutSubviews):
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if let header = tableView.tableHeaderView {
let newSize = header.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
header.frame.size.height = newSize.height
}
}
Thanks to TravMatth for the original answer.
If you're still having problems with layout with the above code sample, there's a slight chance you disabled translatesAutoresizingMaskIntoConstraints on the custom header view. In that case, you need to set translatesAutoresizingMaskIntoConstraints back to true after you set the header's frame.
Here's the code sample I'm using, and working correctly on iOS 11.
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = tableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
tableView.tableHeaderView = headerView
if #available(iOS 9.0, *) {
tableView.layoutIfNeeded()
}
}
headerView.translatesAutoresizingMaskIntoConstraints = true
}
Based on #TravMatth and #NSExceptional's answer:
For Dynamic TableView Header, with multiple line of text(No matter have or not)
My solution is:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let footView = tableView.tableFooterView {
let newSize = footView.systemLayoutSizeFitting(CGSize(width: self.view.bounds.width, height: 0))
if newSize.height != footView.frame.size.height {
footView.frame.size.height = newSize.height
tableView.tableFooterView = footView
}
}
}
tableView.tableFooterView = footView to make sure that your tableview Header or Footer updated.
And if newSize.height != footView.frame.size.height helps you not to be called this method many times
I use the accepted answer for a long time and it always worked for me, until today, when I used a multiple lines label in a complex table header view, I ran into the same issue #frank61003 had:
it create a blank area with multiple lines label.
So in my case, there were big vertical margins around my label. If label text is just 1 line, then everything is fine. This issue only happens when the label has multiple lines of text.
I don't know the exact reason causing this, but I dug for a while and found a workaround to solve the issue, so I want to leave a reference here in case anyone runs into the same problem.
Optional first step, make sure your multiple lines label has the lowest Content Hugging Priority in your table header view, so it can auto increase to fit its text.
Then, add this calculate label height method to your view controller
private func calculateHeightForString(_ string: String) -> CGFloat {
let yourLabelWidth = UIScreen.main.bounds.width - 20
let constraintRect = CGSize(width: yourLabelWidth, height: CGFloat.greatestFiniteMagnitude)
let rect = string.boundingRect(with: constraintRect,
options: .usesLineFragmentOrigin,
// use your label's font
attributes: [.font: descriptionLabel.font!],
context: nil)
return rect.height + 6 // give a little extra arbitrary space (6), remove it if you don't need
}
And use the method above to configure your multiple lines label in viewDidLoad
let description = "Long long long ... text"
descriptionLabel.text = description
// manually calculate multiple lines label height and add a constraint to avoid extra space bug
descriptionLabel.heightAnchor.constraint(equalToConstant: calculateHeightForString(description)).isActive = true
This solved my issue, hope it can work for you too.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if let headerView = self.tableView.tableHeaderView {
let headerViewFrame = headerView.frame
let height = headerView.systemLayoutSizeFitting(headerViewFrame.size, withHorizontalFittingPriority: UILayoutPriority.defaultHigh, verticalFittingPriority: UILayoutPriority.defaultLow).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
self.tableView.tableHeaderView = headerView
}
}
}
Problem in calculating label size when using horizontal or vertical fitting
If all constraint is added, this will work:
headerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
**My Working Solution is:
Add this function in viewcontroller**
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
guard let headerView = myTableView.tableHeaderView else { return }
let height = headerView.systemLayoutSizeFitting(UILayoutFittingCompressedSize).height
var headerFrame = headerView.frame
if height != headerFrame.size.height {
headerFrame.size.height = height
headerView.frame = headerFrame
myTableView.tableHeaderView = headerView
if #available(iOS 9.0, *) {
myTableView.layoutIfNeeded()
}
}
headerView.translatesAutoresizingMaskIntoConstraints = true
}
**Add one line in header's view class.**
override func layoutSubviews() {
super.layoutSubviews()
bookingLabel.preferredMaxLayoutWidth = bookingLabel.bounds.width
}
Just implementing these two UITableView delegate methods worked for me:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section
{
return 100;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return UITableViewAutomaticDimension;
}

Image in top background of UITableView - iOS

I can set an image to my TableView background, but the image is in the center of the view.
How can I set the image to top ?
I'm using staticTableView
let image = UIImageView(image: UIImage(named: "img.jpg"))
self.settingsTableView.backgroundView = image
self.settingsTableView.backgroundView?.frame = CGRectZero
self.settingsTableView.backgroundView?.contentMode = UIViewContentMode.ScaleAspectFit
If you're using a static table and theres no chance of changing it you might want to take an approach like this:
override func viewDidLoad() {
super.viewDidLoad()
//Create the UIImage
let image = UIImage(named: "testing")
//Create a container view that will take all of the tableView space and contain the imageView on top
let containerView = UIView(frame: CGRect(x: 0.0, y: 0.0, width: self.view.bounds.width, height: self.view.bounds.height))
//Create the UIImageView that will be on top of our table
let imageView = UIImageView(frame: CGRect(x: 0.0, y: 0.0, width: self.view.bounds.width, height: image!.size.height))
//Set the image
imageView.image = image
//Clips to bounds so the image doesnt go over the image size
imageView.clipsToBounds = true
//Scale aspect fill so the image doesn't break the aspect ratio to fill in the header (it will zoom)
imageView.contentMode = UIViewContentMode.ScaleAspectFit
containerView.addSubview(imageView)
self.tableView.backgroundView = containerView
}
Make the cells or the headers transparent as you wish. I don't know how your UI should work. This method WON'T scroll the imageView but you can simply do it in the scrollView delegate method. Let me know if you need it to scroll and I'll help you out

Resources