I'm trying to rotate a uiview on top of another view using constraints. Here is what I implemented
mediaContainer.addSubview(overlayView)
keepBottomLeft()
overlayView.layoutIfNeeded()
func keepBottomLeft(overlayView: UIView) {
NSLayoutConstraint(item: overlayView, attribute:.width, relatedBy:.equal,toItem: mediaContainer, attribute:.width, multiplier: 0.8, constant: 0).isActive = true
NSLayoutConstraint(item: overlayView, attribute:.leading, relatedBy:.equal, toItem: mediaContainer, attribute:.leading, multiplier: 1, constant: 0).isActive = true
NSLayoutConstraint(item: overlayView, attribute:.height, relatedBy:.equal,toItem: nil, attribute:.notAnAttribute, multiplier: 1.0, constant: 26).isActive = true
NSLayoutConstraint(item: overlayView, attribute:.bottom, relatedBy:.equal, toItem: mediaContainer, attribute:.bottom, multiplier: 1, constant: 0).isActive = true
overlayView.transform = CGAffineTransform(rotationAngle: CGFloat(-M_PI_2))
}
Will it be necessary to use an anchor point? This is what I'm trying to achieve?
What I get is
Can someone point me in the right direction?
Your anchor point is at the yellow view's center; the default. Either you want to move it to the bottom left with layer.anchorPoint = CGPoint(x: 0, y:1) [Note the anchor is in unit coordinates form 0 to 1, not in points]. You will still need an extra translate of x = height.
Alternatively you can concatenate your transforms and move your center to the point you want to rotate about, perform the rotation, and then reverse your translation (this is actually the typical way to do it with 3d transforms).
UPDATE:
Here is a playground. Note that you concatenate the matrices in the reverse order that you want the transforms to be applied. I have an animation in here so you can decompose the transforms and see what each one does:
Move the view to the origin (0,0) (this is the last transform)
Rotate the view 90 degrees clockwise
Move the view over by half of its current width and down so its at the bottom of the superview.
import PlaygroundSupport
import UIKit
class V: UIViewController {
var overlayView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
overlayView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(overlayView)
overlayView.backgroundColor = .yellow
overlayView.heightAnchor.constraint(equalToConstant: 26).isActive = true
overlayView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.8).isActive = true
overlayView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
overlayView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
}
override func viewDidAppear(_ animated: Bool) {
UIView.animate(withDuration: 1) {
let center = self.overlayView.center
let size = self.overlayView.bounds.size
self.overlayView.transform =
CGAffineTransform(translationX: self.view.bounds.size.height - size.width / 2, y: -size.height/2)
.concatenating(CGAffineTransform(rotationAngle: CGFloat.pi/2)
.concatenating(CGAffineTransform(translationX: -center.x, y: -center.y)))
}
}
}
let v = V()
v.view.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
PlaygroundPage.current.liveView = v
Related
I have the a custom view with the following simple constraint
Height 88
Width 88
Center X
Center Y
I tend to make it as circle visually. Here's my code.
extension UIView {
func asCircle() {
self.layer.cornerRadius = self.frame.width / 2;
self.layer.masksToBounds = true
}
}
class ViewController: UIViewController {
#IBOutlet weak var circleView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
circleView.backgroundColor = .red
circleView.asCircle()
// 1. create a gesture recognizer (tap gesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
// 2. add the gesture recognizer to a view
circleView.addGestureRecognizer(tapGesture)
// Currently, the touchable area of circleView is 88 x 88
}
// 3. this method is called when a tap is recognized
#objc func handleTap(sender: UITapGestureRecognizer) {
print("tap")
}
}
What I would like to have is
The touchable area of the circle remains 88 x 88
The red circle however will look visually as 22 x 22
I add the following code, but it doesn't make any change
// We use 33, because 88-33-33 = 22
circleView.layoutMargins = UIEdgeInsets(top: 33, left: 33, bottom: 33, right: 33)
Does anyone know how to achieve so? Preferable without overriding draw function, or add an additional UIView as subview.
Add layer into your circleView and clear the circleView
example:
extension UIView {
func asCircle() {
self.layer.cornerRadius = self.frame.width / 2;
self.layer.masksToBounds = true
}
}
class ViewController: UIViewController {
var circleView: UIView = {
let circle = UIView()
circle.translatesAutoresizingMaskIntoConstraints = false
return circle
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.circleView)
circleView.backgroundColor = .clear
self.circleView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
self.circleView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
let width = NSLayoutConstraint(item: self.circleView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1.0, constant: 88)
let height = NSLayoutConstraint(item: self.circleView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1.0, constant: 88)
self.circleView.addConstraints([width, height])
self.circleView.layoutIfNeeded()
// 1. create a gesture recognizer (tap gesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
// 2. add the gesture recognizer to a view
circleView.addGestureRecognizer(tapGesture)
// Currently, the touchable area of circleView is 88 x 88
}
// 3. this method is called when a tap is recognized
#objc func handleTap(sender: UITapGestureRecognizer) {
print("tap")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let layer = CALayer()
layer.backgroundColor = UIColor.blue.cgColor
layer.frame = CGRect(x: 0, y: 0, width: 22, height: 22)
self.circleView.layer.insertSublayer(layer, at: 1)
layer.cornerRadius = layer.frame.height / 2
layer.position = CGPoint(x: self.circleView.frame.height / 2, y: self.circleView.frame.height / 2)
}
}
I am having trouble using the Hero Library to dismiss my ViewController with custom animation.
In the end I would like to have pretty much the exact same animation as in this video:
preferred dismiss animation
So far my dismiss animation looks like this:
my dismiss animation so far
I am having 3 major problems which I can not figure out:
1.
When presenting/dismissing my ViewController there seems to be this white background behind my 2nd ViewController but I would like to just cover my first ViewController with the 2nd without any white views.
2.
My image disappears after the user starts swiping down behind my views when instead it should not change its position (preferred dismiss animation) until the view actually dismisses. Same things goes for the add-Button in the bottom right corner.
3. Like in the preferred dismiss animation my the backgroundView(lightGray View in the 2nd video) should dismiss a bit fast then the other subviews. I tried using the cascade-modifier but couldn't get that effect.
This is my 2nd ViewController:
override func viewDidLoad() {
super.viewDidLoad()
self.wishlistBackgroundView.hero.isEnabled = true
self.wishlistBackgroundView.heroID = "wishlistView"
self.wishlistBackgroundView.hero.modifiers = [.fade, .translate(CGPoint(x: 0, y: 800), z: 20)]
// adding panGestureRecognizer
panGR = UIPanGestureRecognizer(target: self,
action: #selector(handlePan(gestureRecognizer:)))
view.addGestureRecognizer(panGR)
self.wishlistLabel.text = wishList.name
self.wishlistImage.image = wishList.image
self.theTableView.wishList = wishList.wishData
self.theTableView.tableView.reloadData()
view.addSubview(wishlistBackgroundView)
view.addSubview(dismissWishlistViewButton)
view.addSubview(menueButton)
wishlistBackgroundView.addSubview(wishlistView)
wishlistBackgroundView.addSubview(wishlistLabel)
wishlistBackgroundView.addSubview(wishlistImage)
wishlistView.addSubview(theTableView.tableView)
wishlistView.addSubview(addWishButton)
NSLayoutConstraint.activate([
// constrain wishlistView
wishlistBackgroundView.topAnchor.constraint(equalTo: view.topAnchor),
wishlistBackgroundView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
wishlistBackgroundView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
wishlistBackgroundView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
wishlistView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 160.0),
wishlistView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
wishlistView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 0),
wishlistView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: 0),
// constrain wishTableView
theTableView.view.topAnchor.constraint(equalTo: wishlistView.topAnchor, constant: 60.0),
theTableView.view.bottomAnchor.constraint(equalTo: wishlistView.bottomAnchor, constant: 0),
theTableView.view.leadingAnchor.constraint(equalTo: wishlistView.safeAreaLayoutGuide.leadingAnchor, constant: 30.0),
theTableView.view.trailingAnchor.constraint(equalTo: wishlistView.safeAreaLayoutGuide.trailingAnchor, constant: -30.0),
// constrain dismissButton
dismissWishlistViewButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
dismissWishlistViewButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 23.0),
// constrain menueButton
menueButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
menueButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -25.0),
// constrain wishlistImage
wishlistImage.topAnchor.constraint(equalTo: wishlistView.topAnchor, constant: -70),
wishlistImage.leadingAnchor.constraint(equalTo: wishlistView.leadingAnchor, constant: 30),
wishlistImage.widthAnchor.constraint(equalToConstant: 90),
wishlistImage.heightAnchor.constraint(equalToConstant: 90),
//constrain wishlistlabel
wishlistLabel.topAnchor.constraint(equalTo: wishlistView.topAnchor, constant: -47),
wishlistLabel.leadingAnchor.constraint(equalTo: wishlistImage.leadingAnchor, constant: 100),
addWishButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -20),
addWishButton.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -40),
])
// set DeleteWishDelegate protocol for the table
theTableView.deleteWishDelegate = self
}
// define a small helper function to add two CGPoints
func addCGPoints (left: CGPoint, right: CGPoint) -> CGPoint {
return CGPoint(x: left.x + right.x, y: left.y + right.y)
}
// handle swqipe down gesture
#objc private func handlePan(gestureRecognizer:UIPanGestureRecognizer) {
// calculate the progress based on how far the user moved
let translation = panGR.translation(in: nil)
let progress = translation.y / 2 / view.bounds.height
switch panGR.state {
case .began:
// begin the transition as normal
dismiss(animated: true, completion: nil)
case .changed:
Hero.shared.update(progress)
// update views' position based on the translation
let viewPosition = CGPoint(x: wishlistBackgroundView.center.x, y: translation.y + wishlistBackgroundView.center.y)
Hero.shared.apply(modifiers: [.position(viewPosition)], to: self.wishlistBackgroundView)
default:
// finish or cancel the transition based on the progress and user's touch velocity
if progress + panGR.velocity(in: nil).y / view.bounds.height > 0.3 {
Hero.shared.finish()
} else {
Hero.shared.cancel()
}
}
}
I couldn't find any good tutorials on this nor anything on these topics on git. If anyone knows even one answer to any of the problems I am more than happy.
Technically these are 3 questions but they are quite related. If this is against SO rules, I am happy to ask them separately.
I have a cake of 3 UIView layers with programmatically constraints.
Constructed function for programmatically set up constraints:
func setupViewConstraints(item:UIView, leadingTo:NSLayoutXAxisAnchor, leadingCon:CGFloat,
trailingTo:NSLayoutXAxisAnchor, trailingCon:CGFloat, topTo:NSLayoutYAxisAnchor,
topCon:CGFloat, bottomTo:NSLayoutYAxisAnchor, bottomCon:CGFloat) {
item.translatesAutoresizingMaskIntoConstraints = false
item.leadingAnchor.constraint(equalTo: leadingTo, constant: leadingCon).isActive = true
item.trailingAnchor.constraint(equalTo: trailingTo, constant: trailingCon).isActive = true
item.topAnchor.constraint(equalTo: topTo, constant:topCon).isActive = true
item.bottomAnchor.constraint(equalTo: bottomTo, constant:bottomCon).isActive = true
}
The lowest base layer is lightGray.
view = UIView()
view.backgroundColor = .lightGray
The 2nd layer contains 2 UIView (red and blue) with constraints .
let red = UIView()
red.backgroundColor = .red
view.addSubview(red)
setupViewConstraints(item: red, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: -(view.frame.width)*0.2), topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: -(view.frame.width)*0.8)
let blue = UIView()
blue.backgroundColor = .blue
view.addSubview(blue)
setupViewConstraints(item: blue, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: -(view.frame.width)*0.2), topTo: red.bottomAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)
And on top i have yellow UIView layer, which overlaps all the lower layers.
let yellow = UIView()
yellow.backgroundColor = .yellow
view.addSubview(yellow)
setupViewConstraints(item: yellow, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: 0, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)
Also, i have UINavigationBar with UINavigationItem inside the yellow UIView.
//Add navigation item and buttons
naviItem = UINavigationItem()
naviItem.setRightBarButton(UIBarButtonItem(barButtonSystemItem:.add, target:self, action:#selector(goToDestVC)), animated: true)
naviItem.setLeftBarButton(UIBarButtonItem(image: UIImage(named: "hamburger_slim_30"), style: .plain, target: self, action: #selector(hamburgerBtnPressed)), animated: true)
//Add navigation bar with transparent background
naviBar = UINavigationBar()
naviBar.setBackgroundImage(UIImage(), for: .default)
naviBar.shadowImage = UIImage()
naviBar.isTranslucent = true
// Assign the navigation item to the navigation bar
naviBar.items = [naviItem]
view.addSubview(naviBar)
setupViewConstraints(item: naviBar, leadingTo: yellow.leadingAnchor, leadingCon: 0, trailingTo: yellow.trailingAnchor, trailingCon: 0, topTo: yellow.topAnchor, topCon: 0, bottomTo: yellow.bottomAnchor, bottomCon: -(view.frame.height)*0.9))
And i have hamburgerBtnPressed function, which should shift the yellow layer to the right by 80% (I change the values of leading and trailing constants by 80%), but this does not work!!!
var hamburgerMenuIsVisible = false
#objc func hamburgerBtnPressed(_ sender: Any) {
if !hamburgerMenuIsVisible {
let menuWidth = (self.view.frame.width)*0.8
setupViewConstraints(item: layoutView, leadingTo: view.leadingAnchor, leadingCon: menuWidth, trailingTo: view.trailingAnchor, trailingCon: menuWidth, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)
hamburgerMenuIsVisible = true
} else {
setupViewConstraints(item: layoutView, leadingTo: view.leadingAnchor, leadingCon: 0, trailingTo: view.trailingAnchor, trailingCon: 0, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)
hamburgerMenuIsVisible = false
}
// layoutIfNeeded() lays out the subviews immediately and forces the layout before drawing
UIView.animate(withDuration: 0.2, delay:0.0, options: .curveEaseIn, animations: {
self.view.layoutIfNeeded()
}) { (animationComplete) in
print("Animation is complete!")
}
}
But if I change the values of leading and trailing constants to negative, everything will work, and the menu will shift to the left without any problems.
let menuWidth = -(self.view.frame.width)*0.8
Please explain.. what's the issue? Why the yellow UIView shifted to the left with negative values of constraints, and does not work with positive values of constraints? and gives an error:
Probably at least one of the constraints in the following list is one you don't want.
(
"<NSLayoutConstraint:0x6040002853c0 UIView:0x7fa947c35850.trailing == UIView:0x7fa947e1d2d0.trailing (active)>",
"<NSLayoutConstraint:0x604000092750 UIView:0x7fa947c35850.trailing == UIView:0x7fa947e1d2d0.trailing + 331.2 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x604000092750 UIView:0x7fa947c35850.trailing == UIView:0x7fa947e1d2d0.trailing + 331.2 (active)>
Update: I have choose Option 2:
Keep a reference to the constraint that you want to change and just adjust its constant. Need to call setNeedsLayout too before layoutIfNeeded.
Updated code:
var leadingC: NSLayoutConstraint!
var trailingC: NSLayoutConstraint!
var yellow: UIView!
loadView():
yellow = UIView()
yellow.backgroundColor = .yellow
view.addSubview(yellow)
//Set up leading and trailing constraints for handling yellow view shift
leadingC = yellow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
trailingC = yellow.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0)
//Put leadingC.constant and trailingC.constant into the function
setupViewConstraints(item: yellow, leadingTo: view.leadingAnchor, leadingCon: leadingC!.constant, trailingTo: view.trailingAnchor, trailingCon: trailingC.constant, topTo: view.topAnchor, topCon: 0, bottomTo: view.bottomAnchor, bottomCon: 0)
Updated Hamburger function:
#objc func hamburgerBtnPressed(_ sender: Any) {
if !hamburgerMenuIsVisible {
let menuWidth = (self.view.frame.width)*0.8
leadingC!.constant = menuWidth
trailingC!.constant = menuWidth
print(leadingC.constant, trailingC.constant)
hamburgerMenuIsVisible = true
} else {
leadingC!.constant = 0
trailingC!.constant = 0
hamburgerMenuIsVisible = false
}
// layoutIfNeeded() lays out the subviews immediately and forces the layout before drawing
UIView.animate(withDuration: 0.2, delay:0.0, options: .curveEaseIn, animations: {
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}) { (animationComplete) in
print("Animation is complete!")
}
}
var hamburgerMenuIsVisible = false
I have no errors and "Animation complete!" was printed too, but nothing happens on the screen, no animation.
Firstly, it needs negative values because the constraints need to be set up in the correct direction. Change this and you can remove all of those negative constants:
item.leadingAnchor.constraint(equalTo: leadingTo, constant: leadingCon).isActive = true
item.trailingAnchor.constraint(equalTo: trailingTo, constant: trailingCon).isActive = true
item.topAnchor.constraint(equalTo: topTo, constant:topCon).isActive = true
item.bottomAnchor.constraint(equalTo: bottomTo, constant:bottomCon).isActive = true
to
item.leadingAnchor.constraint(equalTo: leadingTo, constant: leadingCon).isActive = true
trailingTo.constraint(equalTo: item.trailingAnchor, constant: trailingCon).isActive = true
item.topAnchor.constraint(equalTo: topTo, constant:topCon).isActive = true
bottomTo.constraint(equalTo: item.bottomAnchor, constant:bottomCon).isActive = true
Secondly, every time you call setupViewConstraints you are creating and activating another set of constraints.
Option 1:
Remove all constraints for the yellow view before setting them up again.
Option 2:
Keep a reference to the constraint that you want to change and just adjust its constant. You may need to call setNeedsLayout too before layoutIfNeeded.
Option 3:
Add 2 contraints. The initial leading constraint, and one with the width you desire. Change the priority of the first constraint to 999 (default is 1000) and toggle the isActive property of the other when you want to show/hide the menu.
let leading = yellow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0)
leading.priority = UILayoutPriority(999)
leading.isActive = true
let otherConstraint = yellow.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: (view.frame.width)*0.8)
otherConstraint.isActive = false // toggle this property to show/hide
Option 2 is probably going to be the best for performance. From the apple docs:
Setting the constant on an existing constraint performs much better
than removing the constraint and adding a new one that's exactly like
the old except that it has a different constant
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])
}
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);`