Adding a UILabel to a subview is making the view it disappear - ios

I am trying to create the following view heirarchy
Base UIView-> UIScrollView -> UIView -> UILabel
In the Storyboard I created the view->UIScrollView and made the Scrollview and the base view the same size and aligned their origins.
I am then adding the subsequent UIView -> UILabels in code.
In the ViewController viewDidLoad() function I am adding the UIView and the UILabel and also setting contraints on the UIScrollView.
I am having trouble adding the UILabel. If I just add the UIView to the scrollView its working fine (I gave them different colors just to see that).
As soon as I add the UILabel to the view I see the following issues --
the UIView seems to be disappearing and the label is getting added to the base View.
The UILabel is always getting added to the top corner of the screen. No matter what I do its not changing.
I have a feeling these two issues are connected and I am not adding the constraints on the label properly.
It will be great if someone can point out the error in my code.
override func viewDidLoad()
{
super.viewDidLoad()
view.backgroundColor = UIColor.orange
let boxViewOrigin = CGPoint(x:0, y:0)
let boxViewSize = CGSize(width: view.bounds.width * 3, height: view.bounds.height * 3)
let boxView = UIView(frame: CGRect(origin: boxViewOrigin, size: boxViewSize))
boxView.backgroundColor = UIColor.blue
boxView.translatesAutoresizingMaskIntoConstraints = false
let scrollView = view.subviews[0] as! UIScrollView
scrollView.addSubview(boxView)
// Add contraints
scrollView.contentSize = boxViewSize //CGSize(width: view.bounds.width * 3, height: view.bounds.height * 3)
scrollView.contentOffset = CGPoint(x: (view.bounds.origin.x + view.bounds.width) , y: (view.bounds.origin.y + view.bounds.height) )
/// ---- Commenting out everything from here to the end of the function makes the UIView appear properly. ------
let nameLabel = UILabel()
nameLabel.translatesAutoresizingMaskIntoConstraints = false
nameLabel.backgroundColor = UIColor.yellow
let nameLabelOrigin = CGPoint(x: boxView.bounds.midX, y: boxView.bounds.midY )
nameLabel.frame = CGRect(origin: nameLabelOrigin, size: CGSize(width: 30, height: 30) )
nameLabel.text = "BB"
//nameLabel.isEnabled = false
nameLabel.isUserInteractionEnabled = false
let verticalConstraint:NSLayoutConstraint = NSLayoutConstraint(item: nameLabel, attribute: NSLayoutAttribute.centerY, relatedBy: NSLayoutRelation.equal, toItem: boxView, attribute: NSLayoutAttribute.centerY, multiplier: 1, constant: 0)
let horizontalConstraint:NSLayoutConstraint = NSLayoutConstraint(item: nameLabel, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: boxView, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0)
boxView.addSubview(nameLabel)
view.addConstraints([verticalConstraint, horizontalConstraint])
}

Related

Autoresize multiline UILabel in Swift

I have tried everything to auto-resize my UILabel in Swift. I can autoresize the text when it is one line but when it is two lines it no longer works. The text in the UILabel changes often happening about every 10 seconds. The code I have tried is:
let direction = UILabel(frame: CGRect(x: 95, y: 10, width: screenWidth - 95, height:100))
direction.numberOfLines = 0
direction.adjustsFontSizeToFitWidth = true
direction.lineBreakMode = .byWordWrapping
direction.textAlignment = .center
direction.minimumScaleFactor = 0.2
direction.font = UIFont.boldSystemFont(ofSize: 40)
view.addSubview(direction)
direction.text = "Ffafdafafdfa fafda dfafaf afa"
func updateDirection(update: String){
direction.text = update
}
The original text "Ffafdafafdfa fafda dfafaf afa" will automatically resize but when updateDirection is called the font size with not be changed from 40. I have also tried setting the number of lines to 2 and removing the .byWordWrapping. How can I get my UILabel to resize automatically?
Below code will keep the frame size and adjust the font size according with direction label content.
let backgroundView = UIView(frame: CGRect(x: 5, y: UINavigationController().navigationBar.frame.height + UIApplication.shared.statusBarFrame.height, width: UIScreen.main.bounds.width - 10, height: UIScreen.main.bounds.width - 100))
let direction = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
direction.backgroundColor = UIColor.green
direction.numberOfLines = 0
direction.textAlignment = .center
direction.font = UIFont.boldSystemFont(ofSize: 40)
direction.adjustsFontForContentSizeCategory = true
direction.adjustsFontSizeToFitWidth = true
direction.text = "This is some multiline label with a background colour" // Set or Initiate random function for your array here.
backgroundView.backgroundColor = UIColor.red
view.addSubview(backgroundView)
backgroundView.addSubview(direction)
Timer.scheduledTimer(timeInterval: 10.0, target: self, selector: #selector(random), userInfo: nil, repeats: true)
direction.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: direction,
attribute: .leading,
relatedBy: .equal,
toItem: backgroundView,
attribute: .leadingMargin,
multiplier: 1.0,
constant: 0.0).isActive = true
NSLayoutConstraint(item: direction,
attribute: .trailing,
relatedBy: .equal,
toItem: backgroundView,
attribute: .trailingMargin,
multiplier: 1.0,
constant: 0.0).isActive = true
NSLayoutConstraint(item: direction,
attribute: .top,
relatedBy: .equal,
toItem: backgroundView,
attribute: .topMargin,
multiplier: 1.0,
constant: 0.0).isActive = true
NSLayoutConstraint(item: direction,
attribute: .bottom,
relatedBy: .equal,
toItem: backgroundView,
attribute: .bottomMargin,
multiplier: 1.0,
constant: 0.0).isActive = true
}
func random(sender: Timer) {
//Place your random func code here.
}
Output:
Try this without Constraints:
let direction = UILabel(frame: CGRect(x: 95, y: 10, width: 375 - 95, height:100))
direction.numberOfLines = 0
direction.adjustsFontSizeToFitWidth = true
direction.textAlignment = .center
direction.minimumScaleFactor = 0.2
direction.font = UIFont.boldSystemFont(ofSize: 40)
view.addSubview(direction)
direction.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard"
I will try this code
It is use full
first create one function
func calchight(strin:String) -> CGFloat
{
let label = UILabel(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.text = strin
label.sizeToFit()
return label.frame.height + 2
}
then call this function in your right place, when you want to resize label
/--------------
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
{
let heit = self.calchight(strin: StringObject)
return (heit)
}
/--------------------------
this code is useful and not require Auto layout containers
UILabel will consider its content size to be one line of text, even when numberOfLines is set to a non-zero value, unless preferredMaxLayoutWidth is also set.
In other words, it can't predict how to wrap the text without preferredMaxLayoutWidth, so it doesn't plan to. It seems like during the autolayout process, a UILabel doesn't know how to balance the priority between its width set by layout constraints outside of it and the wrapping of text.
So it doesn't exert any additional pressure (more than the height of one line) on the vertical dimension even when its Content Compression Resistance is higher than other views', because it will assume that by exerting pressure in the horizontal axis, its content will be displayed.
So the solution is to set preferredMaxLayoutWidth, so that it is able to determine the height of the content after wrapping the text and then it uses that content size to negotiate layout with other views.
You probably want to avoid hard-coding the width of your UILabel. Assuming that its container's width is not going to change based on autolayout, it should be safe to use that. In this case I'm in a UITableViewCell or UICollectionViewCell and the "title label" spans the full width of the cell.
override func layoutSubviews() {
// Tell the label it should wrap text at the same width as this cell's contentView.
titleLabel.preferredMaxLayoutWidth = contentView.frame.width
super.layoutSubviews()
}
Try using function sizetofit() after assigning text to your label.
like:
direction.sizeToFit()

How to add interactive UILabels on top of a UIImageView?

I need to add few labels on top of an UIImageView. The labels' text can be changed by tapping on them. What is the best way to achieve this? I am using Swift programming language. Looking up some solutions on stackoverflow, I found a couple of walkthroughs that use String.drawInRect method to draw some text in a rectangle which is then placed on the UIImageView. But like this I don't think I will be able to change the text, or even recognize a touch event on them. Please help.
UPDATE
My code so far:
override func viewDidLoad() {
super.viewDidLoad()
let img = UIImage(named: "Image")
let imgView = UIImageView(image: img)
self.view.addSubview(imgView)
var myLabel = UILabel()
myLabel.text = "Hello There"
myLabel.textColor = UIColor.redColor()
myLabel.font = UIFont(name: "Marker Felt", size: 20)
myLabel.accessibilityIdentifier = "this is good!"
myLabel.frame = CGRect(x: img!.size.width/2 /* - myLable.width / 2 ? */, y: 0, width: img!.size.width, height: 40)
imgView.addSubview(myLabel)
imgView.userInteractionEnabled = true
myLabel.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: "handlePanGesture:")
myLabel.addGestureRecognizer(tapGesture)
}
func handlePanGesture(sender: UITapGestureRecognizer) {
var senderView = sender.view as! UILabel
print(senderView.text)
senderView.text = "look how i changed!"
print(senderView.accessibilityIdentifier)
}
So far the results are positive I have an image with the label on top of it that can respond to touch events. Now I need to find the label's width so that I can effectively place it in the center when required. Then I need to find a way to place the labels at exact coordinates relative to the image's top left corner as origin.
Any help in these two tasks will be hugely appreciated.
Adding label on ImageView is best approach. but you can also do it by adding button on ImageView.
I created a example where i created a ImageView on storyboard and create its outlet in ViewController class and in viewDidLoad i created a label and add it to label and add UITapGestureRecognizer to label. when user taps label we changed the label text and it's position.
class ViewController: UIViewController {
#IBOutlet weak var winterImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel(frame: CGRect(x: 10, y: 0, width: self.winterImageView.frame.width - 10, height: 30))
label.textColor = UIColor.redColor()
label.userInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
label.addGestureRecognizer(tapGesture)
label.text = "Is Winter is coming, My Friend?"
self.winterImageView.addSubview(label)
}
Change label text and position in handleTap
/// handle tap here
func handleTap(sender: UITapGestureRecognizer) {
let senderView = sender.view as! UILabel
senderView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint(item: senderView, attribute: .CenterX, relatedBy: .Equal, toItem: self.winterImageView, attribute: .CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: senderView, attribute: .CenterY, relatedBy: .Equal, toItem: self.winterImageView, attribute: .CenterY, multiplier: 1, constant: 0).active = true
print(senderView.text)
senderView.text = "Yes!!! Winter is coming, My Friend!!"
}
You can download project from here InteractiveLabel
I can see from the other answers and comments related to one another virtually same stuff.If you familiar using Cocoa pods then you will agree with my opinion.Always,Just look around yourself and pick the best.If you want your project goes smooth and steady then JLStickerTextView is your friend and its way to go.It's free,elegant and more vibrant label customisation project available to everyone and the best thing about this project is written in handy Swift.
Github Link: https://github.com/luiyezheng/JLStickerTextView
Features
You can add multiple Text to StickerTextView at the same time
Multiple line Text support
Rotate, resize the text with one finger
Set the Color, alpha, font, alignment, TextShadow, lineSpacing...... of the text
StickerTextView also handle the process of rendering text on Image
Written in Swift
Note: In, My personal opinion.Way, the code been written in this projects simply superb and properly categorised.
Avaliable Text Attributes Reference:
MainView Screenshot from the project:
Output from my personal project based on JLStickerTextView.I, hope you will consider it.If you need any more information let me know...
github.com/khush004/StickerView/tree/master
here is code of JLStickerTextView which is error free with compatibility of swift 3.0
You can use a label and add a gesture recognizer from which you can set an action.
EDIT (responding to OP comment) :
Basically you put an UILabel on top of your card, set a gesture recognizer on it, and set a hidden UITextField at the same position as your label. This way when you tap on it, you specify in your gesture recognizer method that the UI must set label as hidden and textfield as visible. When you're done (end editing), just save your changes and update the UI.
If you just want to center align your UILabel and UIImageView, you can use AutoLayout constraint.
NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: imageView, attribute: .CenterX, multiplier: 1, constant: 0).active = true
NSLayoutConstraint(item: label, attribute: .CenterY, relatedBy: .Equal, toItem: imageView, attribute: .CenterY, multiplier: 1, constant: 0).active = true
func handlePanGesture(sender: UITapGestureRecognizer) {
let senderView = sender.view as! UILabel
print(senderView.text)
senderView.textColor = UIColor.redColor()
senderView.text = "look how i changed!"
print(senderView.accessibilityIdentifier)
}
Ouput :
sender.view?.frame
▿ Optional
▿ Some : CGRect
▿ origin : CGPoint
- x : 0.0
- y : 0.0 { ... }
▿ size : CGSize
- width : 335.0
- height : 28.0
I use CALayers, gesture recognizers, and the hitTest method of the layers. Sample code below:
class ImageView: UIImageView {
let tapGesture = UITapGestureRecognizer()
let redLayer = CATextLayer()
var redHitCounter:Int = 0
let greenLayer = CATextLayer()
var greenHitCounter:Int = 0
override init(frame: CGRect) {
super.init(frame: frame)
setUpClickableLayers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpClickableLayers()
}
private func setUpClickableLayers() {
self.isUserInteractionEnabled = true
tapGesture.numberOfTapsRequired = 1
tapGesture.addTarget(self, action: #selector(changeText))
self.addGestureRecognizer(tapGesture)
redLayer.frame = CGRect(x: 40, y: 40, width: 100, height: 40)
redLayer.backgroundColor = UIColor.red.cgColor
redLayer.string = String(redHitCounter)
redLayer.alignmentMode = kCAAlignmentCenter
self.layer.addSublayer(redLayer)
greenLayer.frame = CGRect(x: 40, y: 140, width: 100, height: 40)
greenLayer.backgroundColor = UIColor.green.cgColor
greenLayer.string = String(redHitCounter)
greenLayer.alignmentMode = kCAAlignmentCenter
self.layer.addSublayer(greenLayer)
}
internal func changeText(_ recognizer:UITapGestureRecognizer) {
let p = recognizer.location(in: self)
if (redLayer.hitTest(p) != nil) {
redHitCounter += 1
redLayer.string = String(redHitCounter)
} else if (greenLayer.hitTest(p) != nil) {
greenHitCounter += 1
greenLayer.string = String(greenHitCounter)
}
}
}
A few notes:
(1) Remember to set your UIImageView's isUserInteractionEnabled to true. It took me an hour to debug why my UIImageView was seeing gestures!
(2) The hitTest() method works for the CALayer and all subclasses. Just remember to make the layer large enough to work on fat fingers.
(3) You can also use the pan and pinch gestures to move, rotate, and resize the target layer.

How to make UIImageView display properly? SWIFT [duplicate]

I'm trying to make a UIView image for my background in swift using pattern image. The code I have works well except for the fact that I want the image to take the whole screen. My code looks like this: self.view.backgroundColor = UIColor(patternImage: UIImage(named: "backgroundImage")!)
Does anyone know how to make the background an image that will take up the whole screen, and would scale when appearing on different iPhone screen sizes?
Note That:
I posted this answer from my old account (which is deprecated for me and I can't access it anymore), this is my improved answer.
You can do it programmatically instead of creating an IBOutlet in each view.
just create a UIView extension (File -> New -> File -> Swift File -> name it whatever you want) and add:
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: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}}
Now, you can use this method with your views, for example:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
}
Just add your UIImageView positioned
centered and with all edges snapping to the edges. Leave it there and
click on the right bottom corner as shown below and now go ahead and
add 4 constrains to Top, Bottom, Left and Right Edges.
Now just select your image view and using the IB inspector select how
you would like your image: fill or fit as you can see as follow:
This is the updated answer of my previous one.
As the same approach of my previous answer, You can create an extension of UIView and add addBackground() method to it, as follows:
Remember: if you are adding it in a new .swift file, remember to add import UIKit
extension UIView {
func addBackground(imageName: String = "YOUR DEFAULT IMAGE NAME", contentMode: UIView.ContentMode = .scaleToFill) {
// setup the UIImageView
let backgroundImageView = UIImageView(frame: UIScreen.main.bounds)
backgroundImageView.image = UIImage(named: imageName)
backgroundImageView.contentMode = contentMode
backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(backgroundImageView)
sendSubviewToBack(backgroundImageView)
// adding NSLayoutConstraints
let leadingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
}
Note that the updates for this answer are:
Swift 4 code 🙂
Adding -programatically- NSLayoutConstraints: that's because when applying what's mentioned in my previous answer, it works fine for the current device orientation, but not when the application does support both portrait/landscape modes, if the device orientation has been changed, the background imageView size will be the same (same size) and not adapts the new width/height of the device screen, so adding constraints should solve this issue.
Adding default parameters: for more flexibility, you might -sometimes- want to change the default image or even the context mode for you background:
Usage:
Assuming that you want to call it in viewDidLoad():
override func viewDidLoad() {
//...
// you can call 4 versions of addBackground() method
// 1- this will add it with the default imageName and default contextMode
view.addBackground()
// 2- this will add it with the edited imageName and default contextMode
view.addBackground(imageName: "NEW IMAGE NAME")
// 3- this will add it with the default imageName and edited contextMode
view.addBackground(contentMode: .scaleAspectFit)
// 4- this will add it with the default imageName and edited contextMode
view.addBackground(imageName: "NEW IMAGE NAME", contextMode: .scaleAspectFit)
}
Here are your options for scaling!
For the .contentMode property:
ScaleToFill
This will scale the image inside the image view to fill the entire boundaries of the image view.
ScaleAspectFit
This will make sure the image inside the image view will have the right aspect ratio and fit inside the image view’s boundaries.
ScaleAspectFill
This will make sure the image inside the image view will have the right aspect ratio and fill the entire boundaries of the image view.
For this value to work properly, make sure that you have set the clipsToBounds property of the imageview to true.
class SecondViewController : UIViewController {
let backgroundImage = UIImage(named: "centralPark")
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.thirdChoiceField.delegate = self
self.datePicker.minimumDate = NSDate()
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
imageView.image = backgroundImage
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubviewToBack(imageView)
me showing my answer as which Lines of code helps me...
extension file code ....
extension UIView {
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageViewBackground.image = UIImage(named: "Your Background Image Name")
imageViewBackground.contentMode = .scaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}
}
and in view controller file....
override func viewDidLoad() {
super.viewDidLoad()
view.addBackground()
}
Thank You Guys !
Ahmad Fayyas Solution in Swift 3.0:
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x:0, y:0, width: width, height: height))
imageViewBackground.image = UIImage(named: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.scaleAspectFill
self.view.addSubview(imageViewBackground)
self.view.sendSubview(toBack: imageViewBackground)
}
This uses PureLayout. You could just use AutoLayout with a few more lines.
UIImageView* imgView = UIImageView(image: myUIImage)
imgView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(imgView)
self.view.addConstraints(imgView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsMake(0,0,0,0))
I used constraints to make the image "autoLayout". I made a view to show an activity indicator (with full background image), while the view on segue is loading. The code is as follows.
var containerView: UIView = UIView()
var actionIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
private func showActivityIndicator() {
///first I set the containerView and the background image
containerView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(containerView)
adjustConstFullSize(containerView, parentView: self.view)
let backImage = UIImageView(image: UIImage(named: "AppBackImage"))
backImage.contentMode = UIViewContentMode.ScaleAspectFill
backImage.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(backImage)
adjustConstFullSize(backImage, parentView: containerView)
////setting the spinner (activity indicator)
actionIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0)
actionIndicator.center = CGPointMake(containerView.bounds.size.width / 2, containerView.bounds.size.height / 2)
actionIndicator.hidesWhenStopped = true
actionIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
containerView.insertSubview(actionIndicator, aboveSubview: backImage)
///throw the container to the main view
view.addSubview(containerView)
actionIndicator.startAnimating()
}
This is the code for the "adjustConstFullSize" function.
func adjustConstFullSize(adjustedView: UIView!, parentView: UIView!) {
let topConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Top,
relatedBy: .Equal,
toItem: parentView,
attribute: .Top,
multiplier: 1.0,
constant: 0.0)
let leftConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Leading,
relatedBy: .Equal,
toItem: parentView,
attribute: .Leading,
multiplier: 1.0,
constant: 0.0)
let rightConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Trailing,
relatedBy: .Equal,
toItem: parentView,
attribute: .Trailing,
multiplier: 1.0,
constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Bottom,
relatedBy: .Equal,
toItem: parentView,
attribute: .Bottom,
multiplier: 1.0,
constant: 0.0)
parentView.addConstraints([topConstraint, leftConstraint, rightConstraint, bottomConstraint])
}
In the function shown above, I "tied" the containerView constraints to the main view constraints, making the view "full size". I did the same for the UIImageView and also set the contentMode to AspectFill - this is crucial, because we want the image to fill the content without stretching.
To remove the view, after the lazy loading, just use the code below.
private func hideActivityIndicator() {
actionIndicator.stopAnimating()
containerView.removeFromSuperview()
}
For this, I think you'll need to create a UIImageView that is pinned to the parent views top / bottom / left / right. This will make the UIImageView always the match the size of the display. Just make sure you set the content mode on the imageview to be AspectFit
var newImgThumb : UIImageView
newImgThumb = UIImageView(view.bounds)
newImgThumb.contentMode = .ScaleAspectFit
view.addSubview(newImgThumb)
//Don't forget this line
newImgThumb.setTranslatesAutoresizingMaskIntoConstraints(false)
NSDictionary *views =NSDictionaryOfVariableBindings(newImgThumb);
// imageview fills the width of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[newImgThumb]|" options:0 metrics:metrics views:views]];
// imageview fills the height of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[newImgThumb]|" options:0 metrics:metrics views:views]];
`
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
_imgBackGround.frame = CGRectMake(0, 0, screenWidth, screenHeight);`

How do I create UITableView header whose height is determined by the height of its label?

I would like to add a header to my tableView. This header contains 1 UILabel. The header height should be calculated based on the number of lines the label has.
In my code, I'm adding constraints with all the edges of the label <> header. This is my attempt:
//Add header to tableView
header = UIView()
header.backgroundColor = UIColor.yellowColor()
tableView!.tableHeaderView = header
//Create Label and add it to the header
postBody = UILabel()
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont(name: "Lato-Regular", size: 16.0)
postBody.numberOfLines = 0
postBody.backgroundColor = FlatLime()
header.addSubview(postBody)
//Enable constraints for each item
postBody.translatesAutoresizingMaskIntoConstraints = false
header.translatesAutoresizingMaskIntoConstraints = false
//Add constraints to the header and post body
let postBodyLeadingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
postBodyLeadingConstraint.active = true
let postBodyTrailingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
postBodyTrailingConstraint.active = true
let postBodyTopConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
postBodyTopConstraint.active = true
let postBodyBottomConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
postBodyBottomConstraint.active = true
//Calculate header size
let size = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)
var frame = header.frame
frame.size.height = size.height
header.frame = frame
tableView!.tableHeaderView = header
header.layoutIfNeeded()
This is my table:
let nib = UINib(nibName: "MessagesTableViewCell", bundle: nil)
let nibSimple = UINib(nibName: "SimpleMessagesTableViewCell", bundle: nil)
self.tableView!.registerNib(nib, forCellReuseIdentifier: "MessagesTableViewCell")
self.tableView!.registerNib(nibSimple, forCellReuseIdentifier: "SimpleMessagesTableViewCell")
self.tableView!.dataSource = self
self.tableView!.delegate = self
self.tableView!.rowHeight = UITableViewAutomaticDimension
self.tableView!.estimatedRowHeight = 100.0
self.tableView!.separatorStyle = UITableViewCellSeparatorStyle.None
self.tableView!.separatorColor = UIColor(hex: 0xf5f5f5)
self.tableView!.separatorInset = UIEdgeInsetsMake(0, 0, 0, 0)
self.tableView!.clipsToBounds = true
self.tableView!.allowsSelection = false
self.tableView!.allowsMultipleSelection = false
self.tableView!.keyboardDismissMode = .OnDrag
As you can see, the header does not take into account the height of the label (which I did numberOfLines = 0)
UILabels take advantage of UIView's intrinsicContentSize() to tell auto layout what size they should be. For a multiline label, however, the intrinsic content size is ambiguous; the table doesn't know if it should be short and wide, tall and narrow, or anything in between.
To combat this, UILabel has a property called preferredMaxLayoutWidth. Setting this tells a multiline label that it should be at most this wide, and allows intrinsicContentSize() to figure out and return an appropriate height to match. By not setting the preferredMaxLayoutWidth in your example, the label leaves its width unbounded and therefore calculates the height for a long, single line of text.
The only complication with preferredMaxLayoutWidth is that you typically don't know what width you want the label to be until auto layout has calculated one for you. For that reason, the place to set it in a view controller subclass (which it looks like your code sample might be from) is in viewDidLayoutSubviews:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
postBody.preferredMaxLayoutWidth = CGRectGetWidth(postBody.frame)
// then update the table header view
if let header = tableView?.tableHeaderView {
header.frame.size.height = header.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
tableView?.tableHeaderView = header
}
}
Obviously, you'll need to add a property for the postBody label for this to work.
Let me know if you're not in a UIViewController subclass here and I'll edit my answer.
Implementation using the storyboard
In UItableView add on UITableViewCell new UIView and put him UILabel
Connects them via Autolayout
In UILabel put the number of lines to 0.
In ViewDidLoad your UILabel call a method sizeToFit()
and specify a size for UIView, and that will be your HeaderVew headerView.frame.size.height = headerLabel.frame.size.height
Code
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var headerView: UIView!
#IBOutlet weak var headerLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
headerLabel.text = "tableViewdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarningdidReceiveMemoryWarning"
headerLabel.sizeToFit()
headerView.frame.size.height = headerLabel.frame.size.height
}
ScreenShot
TestProject
test project link
The first problem we have is that the header cannot be resized by autolayout, for details, see Is it possible to use AutoLayout with UITableView's tableHeaderView?
Therefore, we have to calculate the height of the header manually, for example:
#IBOutlet var table: UITableView!
var header: UIView?
var postBody: UILabel?
override func viewDidLoad() {
super.viewDidLoad()
let header = UIView()
// don't forget to set this
header.translatesAutoresizingMaskIntoConstraints = true
header.backgroundColor = UIColor.yellowColor()
let postBody = UILabel()
postBody.translatesAutoresizingMaskIntoConstraints = false
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont.systemFontOfSize(16.0)
// don't forget to set this
postBody.lineBreakMode = .ByWordWrapping
postBody.numberOfLines = 0
header.addSubview(postBody)
let leadingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
let trailingConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
let topConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: postBody, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: header, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
header.addConstraints([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
self.table.tableHeaderView = header
self.header = header
self.postBody = postBody
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let text = postBody!.attributedText!
let height = text.boundingRectWithSize(
CGSizeMake(table.bounds.size.width, CGFloat.max),
options: [.UsesLineFragmentOrigin],
context: nil
).height
header!.frame.size.height = height
}
You might also want to use the code in stefandouganhyde's answer. It does not really matter how you calculate the height. The point is that autolayout won't work automatically for tableHeaderView.
Result:
We use NSLayoutManager to quickly estimate the height for items that need to resize based on the text. This is the basic idea:
override class func estimatedHeightForItem(text: String, atWidth width: CGFloat) -> CGFloat {
let storage = NSTextStorage(string: text!)
let container = NSTextContainer(size: CGSize(width: width, height: CGFloat.max))
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(container)
storage.addLayoutManager(layoutManager)
storage.addAttribute(NSFontAttributeName, value: UIFont.Body, range: NSRange(location: 0, length: storage.length))
container.lineFragmentPadding = 0.0
return layoutManager.usedRectForTextContainer(container).size.height
}
Beslan's answer is probably a better fit for your use case, but I find it nice to have more control how the layout is handled.
//may be it will help for you.
header = UIView(frame: CGRectMake(tableview.frame.origin.x,tableview.frame.origin.y, tableview.frame.size.width, 40))
header.backgroundColor = UIColor.yellowColor()
//Create Label and add it to the header
postBody = UILabel(frame: header.frame)
postBody.text = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
postBody.font = UIFont(name: "Lato-Regular", size: 16.0)
postBody.numberOfLines = 0
postBody.backgroundColor = FlatLime()
header.addSubview(postBody)
let maximumLabelSize: CGSize = CGSizeMake(postBody.size.width, CGFloat.max);
let options: NSStringDrawingOptions = NSStringDrawingOptions.UsesLineFragmentOrigin
let context: NSStringDrawingContext = NSStringDrawingContext()
context.minimumScaleFactor = 0.8
let attr: Dictionary = [NSFontAttributeName: postBody.font!]
var size: CGSize? = postBody.text?.boundingRectWithSize(maximumLabelSize, options:options, attributes: attr, context: context).size
let frame = header.frame
frame.size.height = size?.height
header.frame = frame
postBody.frame = frame
tableView!.tableHeaderView = header
you can calculate the height of a label by using its string
let labelWidth = label.frame.width
let maxLabelSize = CGSize(width: labelWidth, height: CGFloat.max)
let actualLabelSize = label.text!.boundingRectWithSize(maxLabelSize, options: [.UsesLineFragmentOrigin], attributes: [NSFontAttributeName: label.font], context: nil)
let labelHeight = actualLabelSize.height

How do you make a background image scale to screen size in swift?

I'm trying to make a UIView image for my background in swift using pattern image. The code I have works well except for the fact that I want the image to take the whole screen. My code looks like this: self.view.backgroundColor = UIColor(patternImage: UIImage(named: "backgroundImage")!)
Does anyone know how to make the background an image that will take up the whole screen, and would scale when appearing on different iPhone screen sizes?
Note That:
I posted this answer from my old account (which is deprecated for me and I can't access it anymore), this is my improved answer.
You can do it programmatically instead of creating an IBOutlet in each view.
just create a UIView extension (File -> New -> File -> Swift File -> name it whatever you want) and add:
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: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.ScaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}}
Now, you can use this method with your views, for example:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addBackground()
}
Just add your UIImageView positioned
centered and with all edges snapping to the edges. Leave it there and
click on the right bottom corner as shown below and now go ahead and
add 4 constrains to Top, Bottom, Left and Right Edges.
Now just select your image view and using the IB inspector select how
you would like your image: fill or fit as you can see as follow:
This is the updated answer of my previous one.
As the same approach of my previous answer, You can create an extension of UIView and add addBackground() method to it, as follows:
Remember: if you are adding it in a new .swift file, remember to add import UIKit
extension UIView {
func addBackground(imageName: String = "YOUR DEFAULT IMAGE NAME", contentMode: UIView.ContentMode = .scaleToFill) {
// setup the UIImageView
let backgroundImageView = UIImageView(frame: UIScreen.main.bounds)
backgroundImageView.image = UIImage(named: imageName)
backgroundImageView.contentMode = contentMode
backgroundImageView.translatesAutoresizingMaskIntoConstraints = false
addSubview(backgroundImageView)
sendSubviewToBack(backgroundImageView)
// adding NSLayoutConstraints
let leadingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1.0, constant: 0.0)
let trailingConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 0.0)
let topConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: backgroundImageView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0.0)
NSLayoutConstraint.activate([leadingConstraint, trailingConstraint, topConstraint, bottomConstraint])
}
}
Note that the updates for this answer are:
Swift 4 code 🙂
Adding -programatically- NSLayoutConstraints: that's because when applying what's mentioned in my previous answer, it works fine for the current device orientation, but not when the application does support both portrait/landscape modes, if the device orientation has been changed, the background imageView size will be the same (same size) and not adapts the new width/height of the device screen, so adding constraints should solve this issue.
Adding default parameters: for more flexibility, you might -sometimes- want to change the default image or even the context mode for you background:
Usage:
Assuming that you want to call it in viewDidLoad():
override func viewDidLoad() {
//...
// you can call 4 versions of addBackground() method
// 1- this will add it with the default imageName and default contextMode
view.addBackground()
// 2- this will add it with the edited imageName and default contextMode
view.addBackground(imageName: "NEW IMAGE NAME")
// 3- this will add it with the default imageName and edited contextMode
view.addBackground(contentMode: .scaleAspectFit)
// 4- this will add it with the default imageName and edited contextMode
view.addBackground(imageName: "NEW IMAGE NAME", contextMode: .scaleAspectFit)
}
Here are your options for scaling!
For the .contentMode property:
ScaleToFill
This will scale the image inside the image view to fill the entire boundaries of the image view.
ScaleAspectFit
This will make sure the image inside the image view will have the right aspect ratio and fit inside the image view’s boundaries.
ScaleAspectFill
This will make sure the image inside the image view will have the right aspect ratio and fill the entire boundaries of the image view.
For this value to work properly, make sure that you have set the clipsToBounds property of the imageview to true.
class SecondViewController : UIViewController {
let backgroundImage = UIImage(named: "centralPark")
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.thirdChoiceField.delegate = self
self.datePicker.minimumDate = NSDate()
imageView = UIImageView(frame: view.bounds)
imageView.contentMode = .ScaleAspectFill
imageView.clipsToBounds = true
imageView.image = backgroundImage
imageView.center = view.center
view.addSubview(imageView)
self.view.sendSubviewToBack(imageView)
me showing my answer as which Lines of code helps me...
extension file code ....
extension UIView {
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: width, height: height))
imageViewBackground.image = UIImage(named: "Your Background Image Name")
imageViewBackground.contentMode = .scaleAspectFill
self.addSubview(imageViewBackground)
self.sendSubviewToBack(imageViewBackground)
}
}
and in view controller file....
override func viewDidLoad() {
super.viewDidLoad()
view.addBackground()
}
Thank You Guys !
Ahmad Fayyas Solution in Swift 3.0:
func addBackground() {
let width = UIScreen.main.bounds.size.width
let height = UIScreen.main.bounds.size.height
let imageViewBackground = UIImageView(frame: CGRect(x:0, y:0, width: width, height: height))
imageViewBackground.image = UIImage(named: "YOUR IMAGE NAME GOES HERE")
// you can change the content mode:
imageViewBackground.contentMode = UIViewContentMode.scaleAspectFill
self.view.addSubview(imageViewBackground)
self.view.sendSubview(toBack: imageViewBackground)
}
This uses PureLayout. You could just use AutoLayout with a few more lines.
UIImageView* imgView = UIImageView(image: myUIImage)
imgView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(imgView)
self.view.addConstraints(imgView.autoPinEdgesToSuperviewEdgesWithInsets(UIEdgeInsetsMake(0,0,0,0))
I used constraints to make the image "autoLayout". I made a view to show an activity indicator (with full background image), while the view on segue is loading. The code is as follows.
var containerView: UIView = UIView()
var actionIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
private func showActivityIndicator() {
///first I set the containerView and the background image
containerView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(containerView)
adjustConstFullSize(containerView, parentView: self.view)
let backImage = UIImageView(image: UIImage(named: "AppBackImage"))
backImage.contentMode = UIViewContentMode.ScaleAspectFill
backImage.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(backImage)
adjustConstFullSize(backImage, parentView: containerView)
////setting the spinner (activity indicator)
actionIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0)
actionIndicator.center = CGPointMake(containerView.bounds.size.width / 2, containerView.bounds.size.height / 2)
actionIndicator.hidesWhenStopped = true
actionIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
containerView.insertSubview(actionIndicator, aboveSubview: backImage)
///throw the container to the main view
view.addSubview(containerView)
actionIndicator.startAnimating()
}
This is the code for the "adjustConstFullSize" function.
func adjustConstFullSize(adjustedView: UIView!, parentView: UIView!) {
let topConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Top,
relatedBy: .Equal,
toItem: parentView,
attribute: .Top,
multiplier: 1.0,
constant: 0.0)
let leftConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Leading,
relatedBy: .Equal,
toItem: parentView,
attribute: .Leading,
multiplier: 1.0,
constant: 0.0)
let rightConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Trailing,
relatedBy: .Equal,
toItem: parentView,
attribute: .Trailing,
multiplier: 1.0,
constant: 0.0)
let bottomConstraint = NSLayoutConstraint(item: adjustedView,
attribute: .Bottom,
relatedBy: .Equal,
toItem: parentView,
attribute: .Bottom,
multiplier: 1.0,
constant: 0.0)
parentView.addConstraints([topConstraint, leftConstraint, rightConstraint, bottomConstraint])
}
In the function shown above, I "tied" the containerView constraints to the main view constraints, making the view "full size". I did the same for the UIImageView and also set the contentMode to AspectFill - this is crucial, because we want the image to fill the content without stretching.
To remove the view, after the lazy loading, just use the code below.
private func hideActivityIndicator() {
actionIndicator.stopAnimating()
containerView.removeFromSuperview()
}
For this, I think you'll need to create a UIImageView that is pinned to the parent views top / bottom / left / right. This will make the UIImageView always the match the size of the display. Just make sure you set the content mode on the imageview to be AspectFit
var newImgThumb : UIImageView
newImgThumb = UIImageView(view.bounds)
newImgThumb.contentMode = .ScaleAspectFit
view.addSubview(newImgThumb)
//Don't forget this line
newImgThumb.setTranslatesAutoresizingMaskIntoConstraints(false)
NSDictionary *views =NSDictionaryOfVariableBindings(newImgThumb);
// imageview fills the width of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[newImgThumb]|" options:0 metrics:metrics views:views]];
// imageview fills the height of its superview
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[newImgThumb]|" options:0 metrics:metrics views:views]];
`
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat screenHeight = screenRect.size.height;
_imgBackGround.frame = CGRectMake(0, 0, screenWidth, screenHeight);`

Resources