Last week I dived into auto layout. While coding my first app today, I faced a strange "bug" with UIScrollView and auto layout. I couldn't find any similar issue on here so I created a new topic.
Here is some example code:
self.view.backgroundColor = UIColor.greenColor()
let view = UIScrollView()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.backgroundColor = UIColor.redColor()
self.view.addSubview(view)
let constraintH = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[view]-|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["view": view])
// two options fixing this bug: "H:|-0-[view]-0-|" or "H:|[view]|"
// only horizontal constraint has this issue
let constraintV = NSLayoutConstraint.constraintsWithVisualFormat("V:|-[view]-|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["view": view])
self.view.addConstraints(constraintH)
self.view.addConstraints(constraintV)
First I thought the reason was iOS 8.3 Beta SDK, but issue is also present with iOS 8.1 SDK. So is it only me or is it some autolayout bug which I should report to Apple?
UPDATE:
I modified the code from UIScrollView to UIView so you can see that this bug is also applied to a normal UIView as well.
The horizontal spaces is the Margin, which is introduced in iOS 8. You could try to run it on an iOS 7.1 simulator and there would be no margin.
You were right, removing the dashes should fix it
let constraintH = NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions(0), metrics: nil, views: ["view": view])
Also, if you are using a UIScrollView and AutoLayout you should read this https://developer.apple.com/library/ios/technotes/tn2154/_index.html
Related
When I am displaying some view in UIPopoverPresentationController and presenting it as popover
popoverCon?.modalPresentationStyle = UIModalPresentationStyle.popover
the content have moved upward toward and a some part is being display in the arrow.
Further I had border around the popover
popoverCon?.view.layer.borderColor = .orange
popoverCon?.view.layer.borderWidth = 1.0;
popoverCon?.view.layer.cornerRadius = 10.0;
popoverCon?.view.layer.masksToBounds = false;
it is not showing toward the part where arrow is but it displays a little of the border line in the tip of the arrow.
This was working fine until iOS 12 but in iOS 13 this issue is coming.
Any suggestions on how I can solve this?
The top of my tableView content was cut off by the arrow. This is how I fixed it in my case (code inserted in my tableViewController Swift file):
override func viewSafeAreaInsetsDidChange() {
if #available(iOS 11.0, *) {
super.viewSafeAreaInsetsDidChange()
self.tableView.contentInset = UIEdgeInsets(top: self.tableView.safeAreaInsets.top, left: 0, bottom: 0, right: 0)
}
}
My solution in Obj-C, for those who need an obj-c solution.
I had previously only popovercontroller, that was creating the error like shown in the question. I renamed it to childController for clarity and created a containing popoverController to make the solution given by #SaintMSent work in my situation of only one view originally. Also used https://stackoverflow.com/a/47076040/2148757 solution and https://useyourloaf.com/blog/self-sizing-child-views/ to resize appropriately since all of my childControllers set the preferred content size frequently.
//Create container popover controller and add child to it
UIViewController* popoverController = [[MyParentPopoverController alloc] init];
[popoverController.view addSubview:childController.view];
[popoverController addChildViewController:childController];
[popoverController setPreferredContentSize:childController.preferredContentSize];
//set popover settings on container
popoverController.modalPresentationStyle = UIModalPresentationPopover;
popoverController.popoverPresentationController.sourceRect = sourceRect;
popoverController.popoverPresentationController.sourceView = buttonView;
popoverController.popoverPresentationController.permittedArrowDirections = direction;
//Fix ios13 'bug' that Apple claims is a feature
UILayoutGuide* guide = popoverController.view.safeAreaLayoutGuide;
childController.view.translatesAutoresizingMaskIntoConstraints = NO;
[childController.view.leadingAnchor constraintEqualToAnchor:guide.leadingAnchor].active = YES;
[childController.view.trailingAnchor constraintEqualToAnchor:guide.trailingAnchor].active = YES;
[childController.view.topAnchor constraintEqualToAnchor:guide.topAnchor].active = YES;
[childController.view.bottomAnchor constraintEqualToAnchor:guide.bottomAnchor].active = YES;
[popoverController.view layoutIfNeeded];
//Show the popover
...
#interface MyParentPopoverController : UIViewController
#end
#implementation MyParentPopoverController
-(void)preferredContentSizeDidChangeForChildContentContainer:(id <UIContentContainer>)container {
[super preferredContentSizeDidChangeForChildContentContainer:container];
[self setPreferredContentSize:container.preferredContentSize];
}
#end
Note: I didn't check for ios11 compatibility because my user base is restricted to not use it.
It is definitely a feature, they want you to use safe area since iOS 11, actually, but it seems now they want to force you to use it
Had the same problem as you, this worked for me
https://useyourloaf.com/blog/safe-area-layout-guide/
Definitely a bug. When you have a situation where you use UIPopoverArrowDirectionAny you will see that the problem only exists when the arrow is at the top or left of the popover and not when the arrow appears at the right or the bottom of the popover. If you make adjustments in your code to compensate it will work if you use UIPopoverArrowDirectionUp or UIPopoverArrowDirectionLeft but will not display correctly using that adjustment when using UIPopoverArrowDirectionAny and the popup appears above or to the right of the target rectangle.
I don't have an 'answer' yet, but I have identified what's going on and why it's so hard to fix.
ios13 UIPopoverViewController showing UITableViewController - Safe Area problems / Missing parts of table
Basically, any UITableView that has headers or footers is going to be broken in iOS 13 unless there's some way to alter the _UITableViewHeaderFooterViewBackground
That is notoriously problematic and doesn't play nicely with Auto-Layout - it's been known about for years, but Apple have never fixed it or made it easier to deal with and more publicly known.
https://www.google.com/url?sa=t&rct=j&q=&esrc=s&source=video&cd=1&cad=rja&uact=8&ved=0ahUKEwibouuozfvkAhVCXRUIHVGsBegQtwIIKjAA&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DajsCY8SjJ1Y&usg=AOvVaw3_U_jy9EWH2dJrM8p-XhDQ
https://spin.atomicobject.com/2016/10/12/auto-layout-uitableview/
I'm unable to push my app to the App Store until I get this sorted.. I hope someone can identify how to manipulate this view so that it stops pushing the boundaries of the table out of whack with AutoLayout which causes this safe area intrusion.
Searching on the internet I got help from following link
Twitter
so I had to add safe area and manage my views accordingly
CGFloat topPadding = 0.0;
if (#available(iOS 11.0, *)) {
topPadding = self.view.safeAreaLayoutGuide.layoutFrame.origin.y;
}
Swift:
var topPadding: CGFloat = 0.0
if #available(iOS 11.0, *) {
topPadding = self.view.safeAreaLayoutGuide.layoutFrame.origin.y
}
but I haven't got solution to the border problem of mine yet.
Edit:
Temporarily I did solved the border problem by creating an invisible view on popover and giving it same frame as safe area and drawing its border.
You should use constraints. And also pay attention to topAnchor. It must be safeAreaLayoutGuide.topAnchor. In my case, it works correctly. For example:
[NSLayoutConstraint activateConstraints:#[
[toolbar.leftAnchor constraintEqualToAnchor:self.view.leftAnchor],
[toolbar.rightAnchor constraintEqualToAnchor:self.view.rightAnchor],
[toolbar.topAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor],
[toolbar.heightAnchor constraintEqualToConstant:50]
]];
Embed the contents of the popover in another view with "safe area relative margins" on. This should have -21,-21 as the origin. Turn off vertical and horizontal auto resizing. Seems to work, although you lose auto-stretching.
Setup your popover's content UIViewController like such:
NSLayoutConstraint.activate([
myContentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
myContentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
myContentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
myContentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor)
])
In iOS 10 we changed the tint of our UITextField clear buttons using the following KVO approach.
let clearButton = registrationTextField.value(forKey: "_clearButton") as! UIButton
if let clearImage = clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate) {
clearButton.setImage(clearImage, for: UIControlState())
clearButton.tintColor = UIColor.white
}
However, with the introduction of iOS 11 clearButton.imageView?.image?.withRenderingMode(.alwaysTemplate) now returns nil.
More specifically clearButton.imageView?.image returns nil.
Are there alternative ways of accessing the image now?
I've noticed that in iOS 10 Beta 5 (about to try Beta 6), AutoLayout constraint animation behaves a bit differently.
For example, this approach does not work the same as it did in previous iOS releases:
[view addConstraints:#[constraints...]];
[view setNeedsUpdateConstraints];
[view layoutIfNeeded];
[UIView animateWithDuration:...
{
/* adjust constraint here... */
[view layoutIfNeeded]; // Only the adjusted constraints since previous layoutIfNeeded() call should animate change with duration.
} completion:{ ... }];
... In my testing, the constraints initially added with addConstraints() will also animate in iOS 10 with the UIView animateWithDuration() block... which is causing some funky/undesirable behavior so far.
For example, setting the left/right constraints in the array (but the vertical constraints in the block) causes the entire view to animate onto the screen diagonally with this approach... which is totally incorrect.
Does anyone know how to do this correctly for both iOS 9 (and below), as well as 10?
Try calling layoutIfNeeded on the view's superview, instead of the view itself.
--
I had a similar problem, my view was supposed to animate from the top at a 0 height, downward to a > 0 height (i.e. (0, 0, 320, 0) to (0, 0, 320, 44)).
Using Xcode 8 with iOS 10, the animation behavior was much different. Instead of animating downward from the top, the view animates up & down from the vertical center of the destination frame.
I fixed this by, instead of calling layoutIfNeeded on the view itself, calling it on the view's superview. Somehow, the behavior was restored.
Hope it helps!
Helpful comment by #Ramiro:
According to the documentation, layoutIfNeeded lays out the subviews (but doesn't contemplate the current view itself). So, in order to update the current view lay out, you need to call layoutIfNeeded from the super view.
In below method :
[super viewDidLoad];
Write this line:
[self.view layoutIfNeeded];
I made a small test changing H and V of a small red view:
self.red_POS_H = NSLayoutConstraint.constraints(withVisualFormat: "H:|-90-[redView]", options: defaultOptions, metrics: nil, views: viewsDictionary)
self.red_POS_V = NSLayoutConstraint.constraints(withVisualFormat: "V:|-30-[redView]", options: defaultOptions, metrics: nil, views: viewsDictionary)
self.view.addConstraints(self.red_POS_V!)
self.view.addConstraints(self.red_POS_H!)
and animated it:
// we hope is only one:
let currV = self.red_POS_V![0]
let currH = self.red_POS_H![0]
UIView.animate(withDuration: 3) {
// Make all constraint changes here
currV.constant = 100
currH.constant = 300
self.view.layoutIfNeeded() // Forces the layout of the subtree animation block and then captures all of the frame changes
}
red view moved correctly, if You comment out single line in animation, it does work, horizontally or vertically.
Small project avalable, if You need it.
how do I change the code below to fix the pickerView being depreciated?
This code has worked well before and did the job. I havent needed to compile for a while and when I was fixing another problem I bumped into this NSRangeException when it hits the subview. The code used to work with a previous version of IOS. Any thoughts would be appreciated.
Terminating app due to uncaught exception 'NSRangeException', reason:
'-[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]'
First throw call stack:... etc etc
After more reading I find that the pickerView is depreciated.
Never will understand why Apple can just change code!
-(void)willPresentActionSheet:(UIActionSheet *)actionSheet {
switch ([actionSheet tag] ) {
case 1://date
{
NSDate *d;
UIDatePicker *pickerView = [[UIDatePicker alloc] initWithFrame:CGRectMake(0, 50, 100, 116)];
[pickerView setTag:100+[actionSheet tag]];
[pickerView setDatePickerMode:UIDatePickerModeDate];
if( DefDate == #"" || DefDate == Nil ) DefDate = [fun0 GetCurrentDate];
d = [fun0 GetDatefromString:DefDate];
[pickerView setDate:d animated:YES];
[actionSheet addSubview:pickerView];
[pickerView release];
NSArray *subViews = [actionSheet subviews];
**// Line below is where is where it dumps:**
[[subViews objectAtIndex: SelectButtonIndex] setFrame:CGRectMake(20, 266, 280, 46)];
[[subViews objectAtIndex:CancelButtonIndex] setFrame:CGRectMake(20, 317, 280, 46)];
}
break;
}
}
- (IBAction)btnDate:(id)sender {
UIActionSheet *asheet = [[UIActionSheet alloc]
initWithTitle:#"Pick the date of your meal"
delegate:self
cancelButtonTitle:#""
destructiveButtonTitle:nil
otherButtonTitles:#"Select"
, nil];
[asheet setTag:1];
[asheet showInView:[self.view superview]];
[asheet setFrame:CGRectMake(0, 117, 320, 383)];
[asheet release];
}
I've tested it with iOS 8.0 (and running on XCode6.1) and above, and your code and UIActionSheet both looks nice.
It can compile, run and showing me the output.
Here's what I get.
Update:
From iOS 8.0 and above, UIActionSheet is depreciated. Also its stop supporting add subviews too. This means device with iOS 8.0 and above will not show you UIDatePicker as subview of it. If notice, nightmare is not the error line you've specified below the comment but its not showing data picker any more inside action sheet.
From Documentation,
UIActionSheet is not designed to be subclassed, nor should you add
views to its hierarchy. If you need to present a sheet with more
customization than provided by the UIActionSheet API, you can create
your own and present it modally with
presentViewController:animated:completion:.
IMHO,
You should upgrade your app and code to start supporting from iOS 8.0 only. UIActionSheet will be replace by UIAlertController. [Suggested]
If you wish to support back iOS 8 then you've to use a third party action sheet which you can use in place of UIActionSheet in all iOS versions. Here's the one, https://github.com/skywinder/ActionSheetPicker-3.0 [Good Alternative]
If you don't want to use third party and still wants to support all iOS versions, then you can runtime check for iOS versions and based on that you can either show UIActionSheet and UIAlertController. [Not Recommended]
Few links which may help you.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertController_class/
http://nshipster.com/uialertcontroller/
Add UIPickerView in UIActionSheet from IOS 8 not working
Showing a UIPickerView with UIActionSheet in iOS8 not working
Action Sheet Picker worked great as I was able to chat directly with the developer to get going. I guess It took me a little while to get going understanding it as I dont program in IOS every day, but once I did its awesome! Thank you to Hemang above for all the suggestions!
ACTION SHEET PICKER
It seems something has changed with iOS8 and now none of my tab bar icons are showing up properly. Most of the time they don't show until the tab is active:
But sometimes they don't show up at all and give me just a big blue box (like whenever I dismiss a view that covered the whole window):
This is what I did pre iOS8:
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
UITabBar *tabBar = tabBarController.tabBar;
UITabBarItem *tabBarItem1 = [tabBar.items objectAtIndex:0];
[tabBarItem1 setFinishedSelectedImage:[UIImage imageNamed:#"paintbrush-white.png"] withFinishedUnselectedImage:[UIImage imageNamed:#"paintbrush-black.png"]];
tabBarItem1.imageInsets = UIEdgeInsetsMake(6, 0, -6, 0);
tabBarItem1.title = #"";
as mentioned, if you take a look at:
https://developer.apple.com/Library/ios/documentation/UIKit/Reference/UITabBarItem_Class/index.html#//apple_ref/occ/instm/UITabBarItem/setFinishedSelectedImage:withFinishedUnselectedImage:
you will notice that this method is deprecated, try to change:
[tabBarItem1 setFinishedSelectedImage:[UIImage imageNamed:#"paintbrush-white.png"] withFinishedUnselectedImage:[UIImage imageNamed:#"paintbrush-black.png"]];
to:
[tabBarItem1 setImage:[[UIImage imageNamed:#"paintbrush-white.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
[tabBarItem1 setSelectedImage:[[UIImage imageNamed:#"paintbrush-black.png"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]];
You may also have problems with the image size, depends of the size of image when testing in iPhone 5 screen and iPhone 6 screen for #2x images
Did you try setSelectedImage:?
UIImage *image = [UIImage imageNamed:#"img.png"]
[tabItem setSelectedImage:image];
It works on my part.
This method is deprecated in iOS 8:
Use initWithTitle:image:selectedImage: or the image and selectedImage properties along with UIImageRenderingModeAlwaysOriginal