Prevent calling of viewDidLayoutSubviews on user interaction with iOS 8 custom keyboard - ios

I'm developing an iOS 8 custom keyboard and I'm facing the following issue:
- (void) viewDidLayoutSubviews{} method from UIInputViewController subclass is called each time user touches the keyboard (tap, swipe etc.). I would like to avoid this, there is no need to call it when user touches the keyboard.
Also I found that if I comment the following lines, viewDidLayoutSubviews is not called anymore when user interacts with keyboard:
NSLayoutConstraint *keyboardButtonLeftSideConstraint = [NSLayoutConstraint constraintWithItem:self.customKeyboardView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0];
NSLayoutConstraint *keyboardButtonBottomConstraint = [NSLayoutConstraint constraintWithItem:self.customKeyboardView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0];
[self.view addConstraints:#[keyboardButtonLeftSideConstraint, keyboardButtonBottomConstraint]];
But I can't get rid of these constraints, because I need them to change keyboard's height. How could I solve this?

I highly recommend you not to use viewDidLayoutSubviews(and also viewWillLayoutSubviews) in keyboard extension. They will be called due to frame changing, constraints changing and so many other events, which will sometimes cause unexpected problems. viewDidAppear can be a replacement.
If you really have to override viewDidLayoutSubviews, try use flags to prevent it from getting incorrectly called.
-(void)viewDidLayoutSubviews {
if (self.alreadyLoaded) {
return;
}
else {
//...
}
}

Related

How to add subview with half-transparent background which won't break and won't be affected by AutoLayout?

I want to show user that application is loading data using some progress bar and label on top of view which will be shown after the data is loaded. This view is not initial, so I cannot use LaunchScreen for these purposes. What's the best way to do so?
The view which will be shown after uses AutoLayout, and it'd better be that view on top uses AutoLayout as well, but those AutoLayouts shouldn't interact with each other in any way.
First create a custom UIView class, and customize the view according to your requirement. i.e. transparent background, add image/label etc. using autolayout/with the help of code.
Now add this custom view (yourSubView) into its containerview and add some constraint so that it place in right location.
The following code will add a subview after keep space 80px in all side (left, right, top, bottom), you can add constraint according to your requirement.
[yourContainerView addSubview:yourSubView];
yourSubView.translatesAutoresizingMaskIntoConstraints =
NO;
[yourContainerView addConstraint:[NSLayoutConstraint constraintWithItem:yourSubView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:yourContainerView attribute:NSLayoutAttributeLeft multiplier:1.0 constant:80.0]];
[yourContainerView addConstraint:[NSLayoutConstraint constraintWithItem:yourSubView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:yourContainerView attribute:NSLayoutAttributeRight multiplier:1.0 constant:80.0]];
[yourContainerView addConstraint:[NSLayoutConstraint constraintWithItem:yourSubView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:yourContainerView attribute:NSLayoutAttributeTop multiplier:1.0 constant:80.0]];
[yourContainerView addConstraint:[NSLayoutConstraint constraintWithItem:yourSubView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:yourContainerView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:80.0]];
What about using something like https://github.com/sgryschuk/SGNavigationProgress?

iOS - replicating a constraint programmatically

I created this constraint in Interface Builder. Without it, the below textview expands upwards as its content grows, with it, the textview expands downwards as its content grows.
How do I create that constraint in programmatically?
Here is what I tried:
[self addConstraint:[NSLayoutConstraint
constraintWithItem:_textView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_internalScrollView //this is the parent view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:0.0]];
but it has no material affect on anything.
The UITextView object I am using is from this library https://github.com/MatejBalantic/MBAutoGrowingTextView but that is a red herring to this question.
Here is what you need to do.
[_internalScrollView addConstraint:[NSLayoutConstraint
constraintWithItem:_textView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:_internalScrollView //this is the parent view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:300.0]]; // constant should be 300 as shown by you in screen shot
btw, the above screen shot shows you are making constraint with top layout guide and not with parent view of textView if that is the case then layout attributes should be changed in the above code according to your needs

MKMapView click on pin causes weird frame changes

I created not custom, simple pin annotations on MKMapView and when I click on it, I'm getting following for my MapView:
It happens only on iOS8.
I figured out that without constraints for mapView - it works fine.
Constraints are simple - just to be fullscreen when rotating device.
here are they:
[self.mapView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.mapView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.mapView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0]];
MapView is added to ViewController.view and I'm not getting any warnings about conflicting constraints so on.
Why it happens?
And what is most unfair why clicking on MapView annotation causes it...
I didn't go to details but for some reasons mapView after clicking on annotation had a center in left/top corner of parent view,
so setting center anchor did the trick:
[self.mapView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = true;
[self.mapView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = true;
I guess this is because I specified height/width constraints but nothing about positioning, so defining top/left constraints,
also could help

Autolayout in UITextField: Auto Layout still required after executing -layoutSubviews

I'm subclassing UITextField to add a label on the left side. I'm using autolayout to layout the label. However, I keep getting this crash:
Here's how I am doing my layout code:
- (void)updateConstraints {
self.segmentLabel.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint;
constraint = [NSLayoutConstraint constraintWithItem:self.segmentLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0.0f];
[self addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.segmentLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0.0f];
[self addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.segmentLabel attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0f];
[self addConstraint:constraint];
constraint = [NSLayoutConstraint constraintWithItem:self.segmentLabel attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:self.segmentWidth];
[self addConstraint:constraint];
[super updateConstraints];
}
When I don't make any adjustments to the textfield, this works fine.
However, if I try to set the placeholder text, I get this exception:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. DDSegmentedTextField's implementation of -layoutSubviews needs to call super.'
However, I'm not overriding -layoutSubviews.
Has anyone encountered this? What am I doing wrong?
Thanks!
So I ran into this same error a few days ago as well. It turns out I was trying to layout subviews inside my UITextfield subclass, setting properties on them, moving them, etc, but was never explicitly telling the view to lay itself out (i.e. calling [self layoutIfNeeded]).
iOS 8 seems to force to a view to layout all its subviews, and then configures constraints on it. iOS 7 won't, and needs you to explicitly tell views to redraw their subviews when you change if you're using autolayout.
In my case, I had subclassed UITextField and added a label to the side. I configured the frame of the label by adding constraints to the UITextfield. One of the public methods I could call on my class was
- (void)setLabelText:(NSString *)newText{
self.sideLabel.text = newText;
}
This caused my application to crash when a view controller appeared containing my subclassed textfield. By adding layoutIfNeeded everything now works fine in iOS7 and iOS8.
- (void)setLabelText:(NSString *)newText{
self.sideLabel.text = newText;
[self layoutIfNeeded];
}
This needs to be called every time you change a part of the view in your subclass. This includes the setup when you add subviews, when you change view properties, anything really. Before the function that's changing your view returns, call layoutIfNeeded on your view. This seems to apply for a few standard UI controls including UITextfield, UITableView and UICollectionView, though I'm sure there are others. I hope this was clear enough and helped solve your problem.
The error you're getting isn't super useful, and didn't even apply in my case. Though I was receiving the exact same error, none of my views implementing layoutSubviews, and thus were all using the [super layoutSubviews] method.
This problem shows up in iOS < 8
While creating the UIView subclass and adding views, we can instruct the control to construct and autolayout first the subviews.
Use this statement when the UI needs to be updated:
[self layoutIfNeeded];
iOS 8+ are handling the issue quite on there own internally.
I had the same issue. This could be caused by wrong constraints, maybe by some set in Storyboard. Try to remove some of them and see if it works to figure out what's wrong

Why am I getting a "Auto Layout still required after executing -layoutSubviews" error every time my app launches now?

Since I added the following code, every time my app opens this UITableViewController it crashes:
self.noArticlesView = [[UIView alloc] init];
self.noArticlesView.translatesAutoresizingMaskIntoConstraints = NO;
self.noArticlesView.backgroundColor = [UIColor colorWithRed:0.961 green:0.961 blue:0.961 alpha:1];
[self.view addSubview:self.noArticlesView];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0.0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.noArticlesView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0.0]];
And it gives me this error:
* Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Auto Layout still required after executing -layoutSubviews. UITableView's implementation of -layoutSubviews needs to call super.'
What on earth am I doing wrong? I call that code in tableView:numberOfRowsInSection: when there's 0 rows.
I was subclassing UIScrollView and received the same error message on iOS 7 (but not 8).
I was overriding layoutSubviews in a manner similar to the following:
- (void)layoutSubviews {
[super layoutSubviews];
// code to scroll the view
}
I resolved the issue by moving the call to super's layoutSubviews to be the last thing in the method:
- (void)layoutSubviews {
// code to scroll the view
[super layoutSubviews];
}
Had the same problem. Added view(s) to self.tableView and used constraints. Do not add the views to the table view via addSubview: but add them as header(s), footer(s) or cells.
[self.view layoutIfNeeded]
Hope this helps
You also need to disable mask translation for the table view.
for me is was this
self.tableView.backgroundView = self.emptyView;
I changed to this
NSComparisonResult order = [[UIDevice currentDevice].systemVersion compare: #"8.0" options: NSNumericSearch];
if (order == NSOrderedSame || order == NSOrderedDescending) {
// OS version >= 8.0
self.tableView.backgroundView = self.emptyView;
}else{
[self.tableView.backgroundView addSubview:self.emptyView];
}
Checkout "Auto Layout still required after executing -layoutSubviews" with UITableViewCell subclass as the question appears to be the same.
I was able to implement the category mentioned in one of the answers which solved the problem for me. However, I had to create the category on the UITableView class instead of the UITableViewCell class as is discussed in that particular answer.
You can add your 'no articles view' as a custom header in the table to make sure it's positioned correctly.
A possible solution is not to add the noArticlesView directly in the table, but is to put the UITableView inside a container UIView (eventually setting table constraints to fit with the container frame) and then constraint your noArticlesView to the container, using the same constraints you set and in the same place in your code, that is inside the -tableView:numberOfRowsInSection: UITableViewDataSource method.
I tested it with a simple example, and it worked.
The changes you need to apply to your code are to replace the UITableViewController with a UIViewController, add a container view (unless you want your table to fit exactly with the view controller's view, in such case this view is the container) and then constraint your noArticleView to the container instead of the table.
My example code is at the bottom of this answer.
I will try to make a possible explanation of the reason of the issue and why this solution works, but consider that part of my explanation is based on guesses so it couldn't be completely exact.
First of all a brief explanation of how the view hierarchy rendering process works in iOS. The first step for the layout engine is to determine the size and position of all views and subviews in the view hierarchy. Usually this is done with an iterative approach, where auto layout constraints are evaluated, in order to determine size and position of views and subviews, and then each view's -layoutSubviews method is called for fine tuning: this means that you can change your layout after constraints are evaluated. What the layout engine requires is that if the -layoutSubviews method changes constraints again, then -[super layoutSubviews] must be called again to allow the iterative process: if this doesn't happen the layout engine raises an exception. In the case of the UITableView my guess is that the tableView:numberOfRowsInSection: method is called somewhere within the internal UITableView layoutSubviews method and this method doesn't call [super layoutSubviews]. So if your table data source method updates internal table constraints, at the end of the method the exception is triggered. The solution I propose works because the only constraint applied to the table is the external constraint towards the container, so evaluated at the first stage of the layout process, while the constraints added in the table data source method are not relevant to the table as they are applied to views external to the table (container and noArticlesView) so they don't affect the internal table view layout process.
//
// RootController.h
#import
#interface RootController : UIViewController
#property (nonatomic,strong) IBOutlet UITableView *table;
#property (nonatomic,strong) IBOutlet UIView *container;
#end
//
// RootController.m
#import "RootController.h"
#implementation RootController
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
UIView *_v = [[UIView alloc] init];
_v.backgroundColor=[UIColor redColor];
_v.translatesAutoresizingMaskIntoConstraints=NO;
[self.container addSubview:_v];
NSLayoutConstraint *_c1 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeTop multiplier:1.0 constant:20.0];
NSLayoutConstraint *_c2 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeBottom multiplier:1.0 constant:-20.0];
NSLayoutConstraint *_c3 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeLeft multiplier:1.0 constant:20.0];
NSLayoutConstraint *_c4 = [NSLayoutConstraint constraintWithItem:_v
attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:self.container attribute:NSLayoutAttributeRight multiplier:1.0 constant:-20.0];
[self.container addConstraints:#[_c1,_c2,_c3,_c4]];
return 0;
}
I unchecked auto layout on the view controller the crash is not occurring now

Resources