How to add interactive UILabels on top of a UIImageView? - ios

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.

Related

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

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])
}

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 create an editable UITextField programmatically [duplicate]

This question already has an answer here:
A UIButton in my subview won't work
(1 answer)
Closed 6 years ago.
I'm using Xcode 7.3.1 to write an app in Swift to run on iOS 9.3. I'm creating UITextFields programmatically, but can't get them to accept focus when I click in one of them. Here's how I create each UITextField:
self.hoursField = UITextField()
self.hoursField!.borderStyle = UITextBorderStyle.Bezel
self.hoursField!.canBecomeFirstResponder()
self.hoursField!.canBecomeFocused()
self.hoursField!.delegate = self
self.hoursField!.enablesReturnKeyAutomatically = true
self.hoursField!.translatesAutoresizingMaskIntoConstraints = false
self.hoursField!.userInteractionEnabled = true
The UITextFields appear exactly where I want them and look exactly as I want them to look, I just can't get them to accept focus so I can edit the text in the field.
I am implementing all the methods in the UITextFieldDelegate protocol, and returning true from all those methods that return a boolean.
Can anyone explain what I'm doing wrong, or not doing?
Each text field is being created and added to a custom subclass of UIView that contains a couple of UILabels in addition to the UITextField. Here's the relevant part of the custom view's init method that creates the text field:
init(frame: CGRect, charge: (code:String, note:String?, hours:Int)) {
super.init(frame: frame)
// Create the hours UITextField
self.hoursField = UITextField()
self.hoursField!.borderStyle = UITextBorderStyle.Bezel
self.hoursField!.canBecomeFirstResponder()
self.hoursField!.canBecomeFocused()
self.hoursField!.contentVerticalAlignment = UIControlContentVerticalAlignment.Center
self.hoursField!.delegate = self
self.hoursField!.enablesReturnKeyAutomatically = true
self.hoursField!.font = UIFont.systemFontOfSize(14)
self.hoursField!.keyboardType = UIKeyboardType.NumberPad
self.hoursField!.returnKeyType = UIReturnKeyType.Done
self.hoursField!.text = String(charge.hours)
self.hoursField!.textAlignment = NSTextAlignment.Right
self.hoursField!.translatesAutoresizingMaskIntoConstraints = false
self.hoursField!.userInteractionEnabled = true
// Add it to the view
self.addSubview(self.hoursField!)
// Create its layout constraints
let hoursTopMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.codeLabel!, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
let hoursLeftMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.codeLabel!, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: self.hourFieldIndent)
let hoursRightMarginConstraint:NSLayoutConstraint = NSLayoutConstraint(item: self.hoursField!, attribute: NSLayoutAttribute.Right, relatedBy: NSLayoutRelation.Equal, toItem: self.hoursField!, attribute: NSLayoutAttribute.Left, multiplier: 1, constant: self.hourFieldWidth)
// Add the layout constraints to the view
self.addConstraints([hoursTopMarginConstraint, hoursLeftMarginConstraint, hoursRightMarginConstraint])
A stripped-down version of my app that shows the problem I'm having can be downloded from https://github.com/ThomBrando/TextFieldDemo.git
Have you tried
self.hoursField!.becomeFirstResponder()
When I implement a texfield programmatically all that I have to do is instantiate the texfield. let field = UITextFeild() set its delegate let field.delegate = self
extension UIViewController: UITextFieldDelegate {
public func textFieldShouldReturn(textField: UITextField) -> Bool {
self.view.endEditing(true)
return false
}
}
I have never used the two lines you had in your attempt.
.canBecomeFirstResponder
.conBecomeFocused
.enablesReturnKeyAutomatically
I would suggest removing those.
Then if you are trying to get the keyboard to dismiss when anywhere else is tapped on the screen I would suggest adding this wrapped up in an extension so you can add it anywhere.
extension OnboardingPage {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
view.addGestureRecognizer(tap)
}
Then just call hideKeyboardWhenTappedAround() in your viewDidLoad that you want that action on.
Hope that helps. :)
Can you give us a little more info? Like what's the frame of your UITextfield? Because i copied your code and i have the same issue but it was fine for me when i added a frame for UITextfield. Also just want to make sure you're adding the textfield as a subview to it's parent view.
Also I would recommend trying on a device if you have one, the simulator sometimes bugs out and things don't work as expected
I think this happens because you are testing it on the simulator with the keyboard disabled. The simulator will then use your computer's keyboard and won't show the iOS keyboard.
Could you try pressing Cmd-K when you select the UITextField?
The problem turns out to be this line:
let frame:CGRect = CGRect(x: 0, y: totalContentHeight, width: 0, height: 0)
let chargeView:CustomView = CustomView(frame: frame, charge: charge)
So your CustomView has zero width, zero height — zero size. Thus, none of its subviews is touchable. The text fields are its subviews. Thus, they are not touchable. They are visible, because the CustomView does not clip to its bounds; but they are outside their superview, so they are not touchable.
To prove this, just change the first line to this:
let frame:CGRect = CGRect(x: 0, y: totalContentHeight, width: 500, height: 100)
Those are not the "right" numbers, but that doesn't matter. The point is, you will find you can now tap on the text field and type in it! So that should give you enough of a clue to get started.

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);`

Animation difference between UIView and UIButton

In my custom table view cell, I have 4 buttons with an animation (shown below). The problem is when I'm using an UIButton, the animation doesn't animate as I wanted. But when I use an UIView, it works exactly as how I want it.
The code is exactly the same with only the difference of using a different type of UIView.
This animation is using an UIButton:
This animation is using an UIView
To make things a bit more clear, the only thing I've replaced in the code is:
// Test with Buttons
let button1 = Button() // Subclass of UIButton
let button2 = Button()
// Test with UIViews
let button1 = UIView()
let button2 = UIView()
Question:
Can someone tell me why a UIButton behaves differently compared to a normal UIView?
Initially I thought by not posting the code, I could make the question easier to read as both tests are using exactly the same code (except for the "element" (element being UIView or UIButton), and thought perhaps the problem lies in the difference between the "elements". I realize now that this was my mistake.
My code:
class CustomView: UIView {
private var base: [NSLayoutConstraint] = []
private var open: [NSLayoutConstraint] = []
var buttons: [UIView] = []
private var active = false
override init(frame: CGRect) {
super.init(frame: frame)
let button1 = CustomButton(frame: CGRectZero, color: UIColor.yellowColor().CGColor)
let button2 = CustomButton(frame: CGRectZero, color: UIColor.redColor().CGColor)
// let button1 = UIView(); button1.backgroundColor = UIColor.yellowColor()
// let button2 = UIView(); button2.backgroundColor = UIColor.redColor()
let views = ["button1": button1, "button2": button2]
buttons = [button1, button2]
buttons.enumerate().forEach {
$0.element.translatesAutoresizingMaskIntoConstraints = false
addSubview($0.element)
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[button\($0.index + 1)]|", options: [], metrics: nil, views: views))
base += [NSLayoutConstraint(item: $0.element, attribute: .Width , relatedBy: .Equal, toItem: buttons.first!, attribute: .Width , multiplier: 1, constant: 0)]
}
open += [NSLayoutConstraint(item: buttons.last!, attribute: .Width, relatedBy: .Equal, toItem: buttons.first!, attribute: .Width, multiplier: 0.33, constant: 0)]
addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[button1]-0.5-[button2]|", options: [], metrics: nil, views: views))
addConstraints(base)
backgroundColor = .blackColor()
clipsToBounds = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func changeState() {
removeConstraints(active ? open : base); addConstraints(active ? base : open)
active = !active
UIView.animateWithDuration(0.5, animations: { self.layoutIfNeeded() })
}
}
Solution:
After posting the code and accidentally changing the background color of the buttons, I noticed that it was behaving accordingly. This made me realize I was using a CAShapeLayer in the buttons which is causing the behaviour seen in the first animation. Now I know what to fix. If this post should be closed or deleted, please tell me so. Then I will delete the answer. And thanks for those who tried to help!
Without you providing more details the only thing I can do is guessing that the behaviour you are witnessing comes down to buttons being less "elastic" than views, in that they have a "natural" size determined by their content (title and/or image) and auto layout really likes enforcing that natural size (as you can see from IB's warnings).
If you need more help, you should provide more details. Are you actually using auto layout? What constraints? What are you animating? etc.

Resources