uiView misaligning inside ViewController for iOS10, but not iOS11 - ios

I recently started using Swift and Xcode and I'm not sure why the bottom constraint for the main view in my viewcontroller is shifted up for iOS 10 devices, whereas it works correctly on iOS11.
I've had something similar to another application where the top view constraint was shifted down for iOS10 but I managed to fix it by overriding viewDidLayoutSubviews() and resetting constraints for devices lower than 11.
Unfortunately, the trick doesn't seem to work this time. If I override the bottom constraint, the bottom anchor is corrected but the top one gets pulled up and part of the view gets hidden by the navigation bar.
I've been trying to find a solution via previous questions (most of them were for the top gap and I haven't managed to modify them to work for the bottom gap I'm getting) and Google, but no luck.
I've attached a screenshot of the issue for clarity. iOS11 on the left, iOS10 on the right.
The override i've tried:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let margins = view.layoutMarginsGuide
NSLayoutConstraint.activate([
graphView.leadingAnchor.constraint(equalTo: margins.leadingAnchor),
graphView.trailingAnchor.constraint(equalTo: margins.trailingAnchor)
])
if #available(iOS 11, *) {
// safe area constraints already set
} else {
let standardSpacing: CGFloat = 0.0
NSLayoutConstraint.activate([
contentView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.bottomAnchor, constant: standardSpacing)])
}
And the result:

You need to set these 4 constraints to your Main View

Related

UITableView still leaves space on top even after disabling contentInset Xcode 9.4.1

I'm trying to place a table view inside of a basic view controller without any padding on the top and nothing I've tried has worked, no matter what I do there is a gap up top. I placed the following code in my viewDidLoad() for the view controller:
if #available(iOS 11, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
I've gone into storyboard and disabled those settings as well manually:
I do have custom cells I'm not sure if that matters, I do add insetting to the cells but even when I remove that code I have that gap.
Here's the code I customized for my cells:
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame = UIEdgeInsetsInsetRect(contentView.frame, UIEdgeInsetsMake(cellSpacingHeight, 0, 0, 0))
}
I've spent a couple hours googling around and I can't figure it out. I'm using the latest swift and Xcode to build this. I even tried to print the values for the tableView's content inset and it came up as all 0s. Does anyone know why I still have the offset or inset up top in my table view?
Wow okay so I finally figured it out, I had set the table view style to Grouped at some point instead of Plain. Once I made it plain, everything worked, I hope this might help someone else in this position!

Safe area layout guides in xib files - iOS 10

I started adapting my app for iPhone X and found an issue in Interface Builder.
The safe area layout guides are supposed to be backwards compatible, according to official Apple videos. I found that it works just fine in storyboards.
But in my XIB files, the safe area layout guides are not respected in iOS 10.
They work fine for the new OS version, but the iOS 10 devices seem to simply assume the safe area distance as zero (ignoring the status bar size).
Am I missing any required configuration? Is it an Xcode bug, and if so, any known workarounds?
Here is a screenshot of the issue in a test project (left iOS 10, right iOS 11):
There are some issues with safe area layout and backwards compatibility. See my comment over here.
You might be able to work around the issues with additional constraints like a 1000 priority >= 20.0 to superview.top and a 750 priority == safearea.top. If you always show a status bar, that should fix things.
A better approach may be to have separate storyboards/xibs for pre-iOS 11 and iOS-11 and up, especially if you run into more issues than this. The reason that's preferable is because pre-iOS 11 you should layout constraints to the top/bottom layout guides, but for iOS 11 you should lay them out to safe areas. Layout guides are gone. Laying out to layout guides for pre-iOS 11 is stylistically better than just offsetting by a min of 20 pixels, even though the results will be the same IFF you always show a status bar.
If you take this approach, you'll need to set each file to the correct deployment target that it will be used on (iOS 11, or something earlier) so that Xcode doesn't give you warnings and allows you to use layout guides or safe areas, depending. In your code, check for iOS 11 at runtime and then load the appropriate storyboard/xibs.
The downside of this approach is maintenance, (you'll have two sets of your view controllers to maintain and keep in sync), but once your app only supports iOS 11+ or Apple fixes the backward compatibility layout guide constraint generation, you can get rid of the pre-iOS 11 versions.
By the way, how are you displaying the controller that you're seeing this with? Is it just the root view controller or did you present it, or..? The issue I noticed has to do with pushing view controllers, so you may be hitting a different case.
Currently, backward compatibility doesn't work well.
My solution is to create 2 constraints in interface builder and remove one depending on the ios version you are using:
for ios 11: view.top == safe area.top
for earlier versions: view.top == superview.top + 20
Add them both as outlets as myConstraintSAFEAREA and myConstraintSUPERVIEW respectively. Then:
override func viewDidLoad() {
if #available(iOS 11.0, *) {
view.removeConstraint(myConstraintSUPERVIEW)
} else {
view.removeConstraint(myConstraintSAFEAREA)
}
}
For me, a simple fix for getting it to work on both versions was
if #available(iOS 11, *) {}
else {
self.edgesForExtendedLayout = []
}
From the documentation: "In iOS 10 and earlier, use this property to report which edges of your view controller extend underneath navigation bars or other system-provided views. ".
So setting them to an empty array makes sure the view controller does not extend underneath nav bars.
Docu is available here
I have combined some of the answers from this page into this, which works like a charm (only for top layout guide, as requested in the question):
Make sure to use safe area in your storyboard or xib file
Constraint your views to the safe areas
For each view which has a constraint attached to the SafeArea.top
Create an IBOutlet for the view
Create an IBOutler for the constraint
Inside the ViewController on viewDidLoad:
if (#available(iOS 11.0, *)) {}
else {
// For each view and constraint do:
[self.view.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
self.constraint.active = NO;
}
Edit:
Here is the improved version I ended up using in our codebase. Simply copy/paste the code below and connect each view and constraints to their IBOutletCollection.
#property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray *constraintsAttachedToSafeAreaTop;
#property (strong, nonatomic) IBOutletCollection(UIView) NSArray *viewsAttachedToSafeAreaTop;
if (#available(iOS 11.0, *)) {}
else {
for (UIView *viewAttachedToSafeAreaTop in self.viewsAttachedToSafeAreaTop) {
[viewAttachedToSafeAreaTop.topAnchor constraintEqualToAnchor:self.topLayoutGuide.bottomAnchor].active = YES;
}
for (NSLayoutConstraint *constraintAttachedToSafeAreaTop in self.constraintsAttachedToSafeAreaTop) {
constraintAttachedToSafeAreaTop.active = NO;
}
}
The count of each IBOutletCollection should be equal. e.g. for each view
there should be its associated constraint
I ended up deleting the constraint to safe area which I had in my xib file.
Instead I made an outlet to the UIView in question, and from code I hooked it up like this, in viewDidLayoutSubviews.
let constraint = alert.viewContents.topAnchor.constraint(equalTo: self.topLayoutGuide.bottomAnchor, constant: 0)
constraint.priority = 998
constraint.isActive = true
This ties a small "alert" to top of screen but makes sure that the contents view within the alert is always below the top safe area(iOS11ish)/topLayoutGuide(iOS10ish)
Simple and a one-off solution. If something breaks, I'll be back 🙄.
This also works:
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 11.0, *) {}
else {
view.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.height - 80).isActive = true
view.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 20).isActive = true
}
}
I added a NSLayoutConstraint subclass to fix this problem (IBAdjustableConstraint), with a #IBInspectable variable, looks like this.
class IBAdjustableConstraint: NSLayoutConstraint {
#IBInspectable var safeAreaAdjustedConstant: CGFloat = 0 {
didSet {
if OS.TenOrBelow {
constant += safeAreaAdjustedConstantLegacy
}
}
}
}
And OS.TenOrBelow
struct OS {
static let TenOrBelow = UIDevice.current.systemVersion.compare("10.9", options: NSString.CompareOptions.numeric) == ComparisonResult.orderedAscending
}
Just set that as the subclass of your constraint in IB and you will be able to make < iOS11 specific changes. Hope this helps someone.
I used this one, add the top safe area layout and connect with outlet
#IBOutlet weak var topConstraint : NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
if !DeviceType.IS_IPHONE_X {
if #available(iOS 11, *) {
}
else{
topConstraint.constant = 20
}
}
}
Found the simpliest solution - just disable safe area and use topLayoutGuide and bottomLayoutGuide + add fixes for iPhone X. Maybe it is not beautiful solution but requires as less efforts as possible

How to debug layout with Multiline UILabel / autolayout in notification content extension

How to debug the following issue? Is there a way how to work around this issue?
There seems to be a bug in iOS 10.2 and below when laying out a multi-line UILabel.
I have a fairly simple UIView subclass which I use in both app and notification content extension, that looks like this:
In the main app, everything is laid out just fine:
When shown in notification content extension on iOS 10.2 and below, the layout is broken. But only when the text is long enough to be broken into multiple lines. Seems like iOS can't calculate correct height of the whole view:
However, this issue seems to be fixed on iOS 10.3 and newer:
I started experimenting with the subviews, specifically by setting fixed height constraints.
Turns out, it was not the label(s) that caused the issue with calculating overall height but the aspect ratio constraint (width:height) on the topmost view.
Programmatically calculating height based on the view's width and setting a height constraint for the affected view helped to fix the issue:
public override func updateConstraints() {
super.updateConstraints()
if #available(iOS 10.2, *) {
imageContainerHeightConstraint.isActive = false
} else {
// FIX: multiline label / aspect ratio / autolayout bug in iOS < 10.2
let ratio: CGFloat = imageContainerAspectRatioConstraint.multiplier
imageContainerHeightConstraint.constant = round(bounds.width/ratio)
imageContainerHeightConstraint.isActive = true
}
}

Keyboard extension loses height in iOS 10 when trying to size automatically in some cases

You can download a sample project demonstrating the issue below here:
https://github.com/DimaVartanian/keyboard-extension-height-bug
When creating a keyboard extension and not specifying a concrete height for its components but instead anchoring them to the view/inputView so that in theory the system will determine their height based on environment and orientation, in some situations that height instead turns into 0 and the keyboard is crushed (with the exception of anything that has a concrete height such as a self sized label or button).
This only seems to occur on iOS 10. On iOS 9, the child views resized correctly to fit the default automatic keyboard height.
There are several scenarios this can manifest and this project demonstrates a basic one. It starts with the basic keyboard extension template with the default "next keyboard" button and the 2 size constraints it comes with:
self.nextKeyboardButton.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
self.nextKeyboardButton.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
Next, we create a single other view that we want to fill the space of the superview without defining a concrete size for itself:
let anotherView = UIView()
anotherView.backgroundColor = UIColor.red
anotherView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(anotherView)
anotherView.leftAnchor.constraint(equalTo: self.view.leftAnchor).isActive = true
anotherView.rightAnchor.constraint(equalTo: self.view.rightAnchor).isActive = true
anotherView.topAnchor.constraint(equalTo: self.view.topAnchor).isActive = true
Now, let's say we just want to anchor this new view to the bottom of our keyboard superview. We would just do something like:
anotherView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor).isActive = true
The result looks like this:
iOS 9
iOS 10
This layout is exactly what we expect. Now instead, let's anchor the new view to the top of our next keyboard button. We get rid of the constraint we just added and replace it with
anotherView.bottomAnchor.constraint(equalTo: self.nextKeyboardButton.topAnchor).isActive = true
Logically, the resulting height should be the same (determined by the system)
The result is now this:
iOS 9
iOS 10
On iOS 9 it behaves as expected but on iOS 10, the flexible height view is resized down to 0 and all that is left is the fixed height button.
There are no messages about conflicting constraints. I'm trying to figure out what could be causing this and why it would only be happening on iOS 10.
Apple has responded to my DTS ticket and told me to file a bug report, so this is actually an iOS 10 bug. I have filed a radar (#28532959) and will update this answer if I ever get a response. If someone else comes up with a concrete solution that allows me to still use autolayout to achieve an automatic height, answers are still accepted.
I got it solved by setting a new constrain for the height.
Here's my workaround. It is a little laggy when the device rotates, but it will do the job until Apple fixes this bug. I first thought it had something to do with inputView.allowSelfSizing, but that variable didn't seem to change anything.
First, declare heightConstraint:
var heightConstraint: NSLayoutConstraint!
In viewDidLoad, Add your custom view:
let nibName: String! = UIDevice.isPhone ? "KeyboardViewiPhone" : "KeyboardViewiPad"
customView = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?.first as! UIView
customView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(customView)
Add a constraint for the width as you would do normally:
let widthConstraint = NSLayoutConstraint(item: view, attribute: .width, relatedBy: .equal, toItem: customView, attribute: .width, multiplier: 1.0, constant: 0.0)
Add a constant constraint for the height:
heightConstraint = NSLayoutConstraint(item: customView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: view.frame.height)
view.addConstraints([widthConstraint, heightConstraint])
Now comes the fix:
override func viewDidLayoutSubviews() {
heightConstraint.constant = view.bounds.height
}
As viewDidLayoutSubviews is called every time view.bounds changes, it will handle orientation changes correctly.
I also faced the same issue. this is because of the Autolayout Constraints. Just remove all constraints. and set auto resizing.
I have also faced the same problem for the custom keyboard extension in Xcode 8.2. This is caused by the auto resizing. In my case, I solved this in the below manner.
Initially, my custom keyboard have 3 views.
In this, I was fixed the trailing, leading, top and height for the first and last view. And place the middle view like in the image.
after that select the middle view and open the show the size inspector in the storyboard. In the size inspector, you will find an option auto resizing. In that select the constraint indicators for that view.
After selecting that you run your project in a device and it will work correctly without missing any view.
Note: - It will work for both portrait and landscape modes. And mainly you don't have to give constraints for the middle view.
IMHO, best working solution is using "Proportional Height". For example, in my case, I finally ended with 2 views. Top one got 0.8 of height of superview, bottom - 0.2. It's not perfect solution, but you can still benefits from autolayout.

UITextView scroll bug iOS9 xcode 7.1.1

I have a simple UITextView that occupies all the screen.
When the text length is bigger than the Textview height, it automatically scrolls to the bottom (see image).
I have already tried
self.textView.scrollRangeToVisible(NSMakeRange(0, 0))
and
self.textView.scrollsToTop = true
and other things without any result.
I'm using iOS 9 with the latest swift and Xcode 7.1.1.
I've found a solution.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.textView.setContentOffset(CGPoint.zero, animated: false)
}
but I still don't understand why it didn't work.
You also try
self.textView.scrollRangeToVisible(NSRange(location:0, length:0))

Resources