Image dimensions changing on rotation - ios

I have an image called sampleImage, and I am trying to stretch it along x and y axis, as follows when it is in landscape and it did not work at all
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator)
{
if (UIDevice.current.orientation == UIDeviceOrientation.portrait)
{
sampleImage.frame = CGRect(x: 0, y: 0, width: 220, height: 120)
}
else
{
sampleImage.frame = CGRect(x: 0.2, y: 0.2, width: 220, height: 120)
}
}
Then, I did in viewDidLayoutSubviews() as follows,
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if (UIDevice.current.orientation == UIDeviceOrientation.portrait)
{
sampleImage.frame = CGRect(x: 0, y: 0, width: 220, height: 120)
}
else
{
sampleImage.frame = CGRect(x: 0.2, y: 0.2, width: 220, height: 120)
}
}
This works if the device is in landscape, but once you rotate it to portrait and back, all the changes are gone.How to make sure that those dimensions stay same on rotation?

Basically, it's all about constraints. First approach to solve your problem is to handle device rotation (good so answer) and change it manually via setNeedsUpdateConstratints method (another good so answer). This way good for heavy screen with complex changes with UI and constraints. In your case you better use specific constant for constraint base on device orientation that can be set in storyboard.
So, follow this steps:
Set Equal Heights and Equal Widths constraints to the UIImageView, then double click for Width Equals: 120 constraint:
Open View as: iPhone ... bottom bar:
Select specific device and landscape orientation at right side and hit the + before Constant at the right panel.
It will automatically set right Width and Height base on selected device and orientation, so you just need to enter the constant for this case. Run the project and see the result.
Portrait:
Landscape:
Note, also don't forget that different devices may have different size classes (compact x compact, compact x regular, etc.), so maybe you will need to add few more constants to handle this problem (see the table at the bottom of the article).
Finally, you solved the problem by working with storyboard, not programmatically. Pros of that - you code not grow up and not responsible for the UI things, cons - you should add the constant to every size class you want to use.

Related

Adjust the MenuBar under Navigation Controller

When I tried to run on different devices, the menuBar on Iphone 8 seems lower than Iphone X, how would I adjust those programmatically for every screen?
This is my code I have for the menuBar
customCollectionView = MenuBar(frame: CGRect(x: 0, y:(navigationController?.navigationBar.frame.height)!, width: view.frame.width, height: 50))
customCollectionView.translatesAutoresizingMaskIntoConstraints = true
customCollectionView.collectionView.isScrollEnabled = false
customCollectionView.collectionView.delegate = self
view.addSubview(customCollectionView)
menubarViewConstraints()
function for menuBar constraints
func menubarViewConstraints(){
customCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
customCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
customCollectionView.topAnchor.constraint(equalTo: (navigationController?.navigationBar.bottomAnchor)!).isActive = true
}
Switch form portrait mode to landscape mode:
The problem here is the safe area on the iPhone X (any potentially future iOS devices).
If you change the y position of your init frame call to be the following it should work.
y:((navigationController?.navigationBar.frame.height)!) + (navigationController?.navigationBar.frame.origin.y) + 30
Basically that will take the height of the navigation bar and add the Y origin position and finally add 30 to it.
There might be a better way to achieve this using the devices safe area, but this is the method that came to mind first that seems like it'll work.

iOS - UITextField: extend the bottom line for ANY resolution

I created a UITextFiled with a bottom line using this:
let Bottomline CALayer = ()
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
TextField.borderStyle = UITextBorderStyle.none
TextField.layer.addSublayer (Bottomline)
and the result of an iPhone 6 (right) is this:
Ok.
✄------------------------
The problem is to run the same application on a Pro iPad, because the
bottom line does not extend following the UITextField, but is shorter
This is the result on iPad Pro:
I do not understand why the bottom line does not follow the UITextField. When I called the bottom line I defined as:
bottomLine.frame CGRect = (x: 0, y: usernameTextField.frame.height-7, width: usernameTextField.frame.width, height: 1)
I have specified that the length of the line at the bottom must be:
width: usernameTextField.frame.width
What's the problem?
EDIT 1: The contrains are correct, because the UITextField adapts to
all types of resolution
EDIT:2 Thanks Matt! Now work!!!
I do not understand why the bottom line does not follow the UITextField
Because it's a layer. Layers do not automatically change size when their superlayer (the text field) changes size.
So, you need to redraw the bottom line every time the text field changes size.
At the moment, though, you are configuring the "bottom line" layer in viewDidLoad. So you are basing it on the frame that the text field has at that moment. But the text field has not yet attained its real size. Then it does change size, and meanwhile your "bottom line" layer just sits there — so now it is the wrong size.
An easy solution is to subclass UITextField and redraw the line every time layoutSubviews is called:
class MyTextField : UITextField {
var lineLayer : CALayer?
override func layoutSubviews() {
super.layoutSubviews()
self.lineLayer?.removeFromSuperlayer()
let bottomLine = CALayer()
bottomLine.frame = CGRect(x: 0, y: self.bounds.height-7, width: self.bounds.width, height: 1)
bottomLine.backgroundColor = UIColor.black.cgColor
self.layer.addSublayer(bottomLine)
self.lineLayer = bottomLine
}
}
If your text field is a MyTextField, it will behave exactly as you desire.

Sizing of Live Views in Swift Playground

I'm having trouble figuring out how to lay out views in Swift Playgrounds for iPad, though this may also be relevant to Mac users.
The following code should create a view with a red square (also a view) that is near the edges of its' super view, but not touching them.
let v = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let sqv = UIView(frame: CGRect(x: 400, y: 400, width: 50, height: 50))
sqv.backgroundColor = .red
v.addSubview(sqv)
PlaygroundPage.current.liveView = v
The result is not what you'd expect:
I suspect I know what is going on here; live views are at a fixed size that is larger than the display area. Some characteristics of the view are ignored when it is acting as the live view. However, I can't find where this is mentioned in the documentation, which vexes me. More importantly, how do I deal with this? I would like to be able to layout simple UIs that change to fit the current size of the live view. I don't know how to address this issue without trial & error and hardcoding, which are two things I would really like to avoid.
I suspect I know what is going on here; live views are at a fixed size that is larger than the display area.
Actually it's more like the other way around. An iPad screen is 1024 points wide (in landscape orientation). The right-hand pane (where it shows your live view) is 512 points wide. The playground forces your root view (v) to fill that pane, inset by 40 points on the left, right, and top (and more on the bottom). So your root view's width is forced to 432 ( = 512 - 2 * 40), less than the 500 you specified.
Views created in code (like yours) have translatesAutoresizingMaskIntoConstraints = true, and a resizing mask of 0, which means don't adjust the view's frame at all when its parent is resized. So the playground resizes your root view to width 432, but your root view doesn't move or resize its subview (sqv).
The easiest fix is to set the autoresizing mask of the subview to express your intent that it remain near the right and bottom edges of the root view. That means it should have flexible top and left margins:
let v = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 500))
let sqv = UIView(frame: CGRect(x: 400, y: 400, width: 50, height: 50))
sqv.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin]
sqv.backgroundColor = .red
v.addSubview(sqv)
PlaygroundPage.current.liveView = v
Result:
let sqv = UIView(frame: CGRect(x: UIScreen.mainScreen().bounds.width-50-1, y:400, width: 50, height: 50))
The above code places your subview 1 point away from the right of the main view. Try changing the value 1 after 50 in x to desired value.

Set stretching parameters for images programmatically in swift for iOS

So if we want to stretch only parts of an image, be it a regular image or a background image, we use the following settings in layout editor:
How do you set those programmatically?
I'm using Xcode 7.2.1
Specifying the cap insets of your image
You can set the stretch specifics by making use of the UIImage method .resizableImageWithCapInsets(_:UIEdgeInsets, resizingMode: UIImageResizingMode).
Declaration
func resizableImageWithCapInsets(capInsets: UIEdgeInsets, resizingMode: UIImageResizingMode) -> UIImage
Description
Creates and returns a new image object with the specified cap insets
and options.
A new image object with the specified cap insets and resizing mode.
Parameters
capInsets: The values to use for the cap insets.
resizingMode: The mode with which the interior of the image is
resized.
Example: custom stretching using the specified cap insets
As an example, let's try to---programmatically---stretch my (current) profile picture along its width, precisely at my right leg (left side from viewing point of view), and leave the rest of the image with its original proportions. This could be comparable to stretching the width of some button texture to the size of its content.
First, let's load our original image foo.png as an UIImage object:
let foo = UIImage(named: "foo.png") // 328 x 328
Now, using .resizableImageWithCapInsets(_:UIEdgeInsets, resizingMode: UIImageResizingMode), we'll define another UIImage instance, with specified cap insets (to the middle of my right leg), and set resizing mode to .Stretch:
/* middle of right leg at ~ |-> 0.48: LEG :0.52 <-| along
image width (for width normalized to 1.0) */
let fooWidth = foo?.size.width ?? 0
let leftCapInset = 0.48*fooWidth
let rightCapInset = fooWidth-leftCapInset // = 0.52*fooWidth
let bar = UIEdgeInsets(top: 0, left: leftCapInset, bottom: 0, right: rightCapInset)
let fooWithInsets = foo?.resizableImageWithCapInsets(bar, resizingMode: .Stretch) ?? UIImage()
Note that 0.48 literal above corresponds to the value you enter for X in the interface builder, as shown in the image in your question above (or as described in detail in the link provided by matt).
Moving on, we finally place the image with cap insets in an UIImageView, and let the width of this image view be larger than the width of the image
/* put 'fooWithInsets' in an imageView.
as per default, frame will cover 'foo.png' size */
let imageView = UIImageView(image: fooWithInsets)
/* expand frame width, 328 -> 600 */
imageView.frame = CGRect(x: 0, y: 0, width: 600, height: 328)
The resulting view stretches the original image as specified, yielding an unproportionally long leg.
Now, as long as the frame of the image has 1:1 width:height proportions (328:328), stretching will be uniform, as if only fitting any image to a smaller/larger frame. For any frame with width values larger than the height (a:1, ratio, a>1), the leg will begin to stretch unproportionally.
Extension to match the X, width, Y and height stretching properties in the Interface Builder
Finally, to thoroughly actually answer your question (which we've really only done implicitly above), we can make use of the detailed explanation of the X, width, Y and height Interface Builder stretching properties in the link provided by matt, to construct our own UIImage extension using (apparently) the same properties, translated to cap insets in the extension:
extension UIImage {
func resizableImageWithStretchingProperties(
X X: CGFloat, width widthProportion: CGFloat,
Y: CGFloat, height heightProportion: CGFloat) -> UIImage {
let selfWidth = self.size.width
let selfHeight = self.size.height
// insets along width
let leftCapInset = X*selfWidth*(1-widthProportion)
let rightCapInset = (1-X)*selfWidth*(1-widthProportion)
// insets along height
let topCapInset = Y*selfHeight*(1-heightProportion)
let bottomCapInset = (1-Y)*selfHeight*(1-heightProportion)
return self.resizableImageWithCapInsets(
UIEdgeInsets(top: topCapInset, left: leftCapInset,
bottom: bottomCapInset, right: rightCapInset),
resizingMode: .Stretch)
}
}
Using this extension, we can achieve the same horizontal stretching of foo.png as above, as follows:
let foo = UIImage(named: "foo.png") // 328 x 328
let fooWithInsets = foo?.resizableImageWithStretchingProperties(
X: 0.48, width: 0, Y: 0, height: 0) ?? UIImage()
let imageView = UIImageView(image: fooWithInsets)
imageView.frame = CGRect(x: 0, y: 0, width: 600, height: 328)
Extending our example: stretching width as well as height
Now, say we want to stretch my right leg as above (along width), but in addition also my hands and left leg along the height of the image. We control this by using the Y property in the extension above:
let foo = UIImage(named: "foo.png") // 328 x 328
let fooWithInsets = foo?.resizableImageWithStretchingProperties(
X: 0.48, width: 0, Y: 0.45, height: 0) ?? UIImage()
let imageView = UIImageView(image: fooWithInsets)
imageView.frame = CGRect(x: 0, y: 0, width: 500, height: 500)
Yielding the following stretched image:
The extension obviously allows for a more versatile use of the cap inset stretching (comparable versatility as using the Interface Builder), but note that the extension, in its current form, does not include any user input validation, so it's up to the caller to use arguments in the correct ranges.
Finally, a relevant note for any operations covering images and their coordinates:
Note: Image coordinate axes x (width) and y (height) run as
x (width): left to right (as expected)
y (height): top to bottom (don't miss this!)

Simulating "falling" objects on UIView

I am just testing some animation of "falling" views. I want to simulate leafs that fall from the sky. I have made this really simple code which is unfinished, but was used to check if I was doing it right.
However, should the "falling" views have auto layout constraints attached to them? Is it bad to just add views to the screen like this when the rest of the view is done with auto layout? I am using Swift 2 and iOS 9.
let rectangleView = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
rectangleView.backgroundColor = UIColor.redColor()
backgroundView.addSubview(rectangleView)
UIView.animateWithDuration(10, delay: 0, options: [UIViewAnimationOptions.CurveEaseIn], animations: {
rectangleView.center = CGPoint(x: rectangleView.center.x, y: 300)
}, completion: nil)
If your views are moving freely on the screen, then I don't see any reason to use auto layout constraints.
There is no issue adding a subview to a UI that uses auto layout and animating it yourself, as you are doing. Not everything on the screen has to be managed by auto layout.

Resources