In iOS 11 buttons and text field are unresponsive being subviews of UIToolBar. Comparing view hierarchy to iOS 10 we see there is a _UIToolBarContentView over all subview of UIToolBar.
For instance, this new layout of the UIToolBar breaks slacktextviewcontroller
https://github.com/slackhq/SlackTextViewController/issues/604
Need a solution working in iOS 10/11.
To solve the problem for iOS11 (compatible with lower versions) you only need
to make layoutSubview right after UIToolBar was added as a subview to UI hierarchy.
In this case _UIToolbarContentView lowers to the first subview of UIToolBar, and you can
add all your subviews higher as before.
For example in ObjC,
UIToolbar *toolbar = [UIToolbar new];
[self addSubview: toolbar];
[toolbar layoutIfNeeded];
<here one can add all subviews needed>
The same problem happens with slacktextviewcontroller
I have solved this problem in my case. I rewrite the layoutSubviews method in subclass of UIToobar and change the userInteractionEnable of _UIToolbarContentView into NO.
- (void)layoutSubviews {
[super layoutSubviews];
NSArray *subViewArray = [self subviews];
for (id view in subViewArray) {
if ([view isKindOfClass:(NSClassFromString(#"_UIToolbarContentView"))]) {
UIView *testView = view;
testView.userInteractionEnabled = NO;
}
}
}
You can just use the hitTest(_:with:) method.
First, create a property contentView in UIToolbar:
open private(set) var contentView: UIView = UIView()
Then, make the contentView's frame the same as the UIToolbar's. For example:
contentView.frame = bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(contentView)
Finally, override the hitTest(_:with:) method:
open override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
if let hitTestView = contentView.hitTest(point, with: event) {
return hitTestView
} else {
return self
}
} else {
return nil
}
}
In this situation, if you want to customize a toolbar by simply adding additional views, you should add them to the contentView so they will be positioned appropriately.
The new UIToolbar object actively uses layout based on constraints, so it is better to override - (void)updateConstraints method. To present custom views over UIToolbar object it is better to subclass it and add custom container view:
- (UIView *)containerView
{
if (_containerView) {
return _containerView;
}
_containerView = [[UIView alloc] initWithFrame:self.bounds];
_containerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
return _containerView;
}
Now you can safely add your custom views to the container view. To make the custom views responsive we need change the order of toolbar subviews after the constraints update:
- (void)updateConstraints
{
[super updateConstraints];
[self bringSubviewToFront:self.containerView];
}
Note, that if you are using UINavigationController with custom toolbar, you should force it to update its layout before adding your custom subviews.
In Swift with autolayout and code only, what worked for me was to do layout as malex mentions just before adding items, but after setting constraints.
Instantiate your toolbar
Add it to your view
Add constraints
toolbar.layoutIfNeeded()
toolbar.setItems([... (your items)], animated: true)
There is an odd way to do it.
[self.textInputbar sendSubviewToBack:[self.textInputbar.subviews lastObject]];
Related
Have UITableviewcell --> inside which i have 4 different UIView and 3 of the views has UIButtons. I want to make UIButton clickable. For the first time the buttons are clickable but when i go to next screen and come back, the buttons don't work. Please let me know how to implement this? Thanks!
PHMyTripView *tripsView=[[PHMyTripView alloc] initWithFrame:CGRectMake(10, 5, self.frame.size.width-20, cell.frame.size.height)];
tripsView.onBaggageClick = ^{
[weakSelf handleBaggagePurchaseTapped];
};
if ([data count]>0) {
[tripsView fillTripData:[data firstObject]];
[tripsView showAncillaries:self.predictiveManager.upcomingCDAirBookingInfo];
}
[cell bringSubviewToFront:tripsView.bagsButton];
[cell.viewPlaceHolder addSubview:tripsView];
This happens because the cells are reusable and if you go to another screen the button may kinda mess round into different cell in UI or functionally.
The way to solve this is to add tag. You can define cell.tag = indexPath.row * 10 or so.
Then when draw the button, you check the condition, if cell.tag == ?, then add your button.
Thank you everyone for your response but they din't work in my case.
Here is the answer. Since the UITableview was reloading when it comes back to the screen.The frames were mis placed and Hence the button was not able to click.
Used this one line code which worked fine.
self.tripsView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
You should turn off delaysContentTouches on each UITableView subview, which is an instance of UIScrollView class.
Subclass UITableView and override initWithFrame method:
.h file
#interface NoDelaysOnTouchTableView : UITableView
#end
And .m file
#import "NoDelaysOnTouchTableView.h"
#implementation NoDelaysOnTouchTableView
- (id) initWithFrame: (CGRect) frame
{
self = [super initWithFrame: frame];
if (self) {
for (id view in self.subviews) {
if ([NSStringFromClass([view class]) isEqualToString: #"UITableViewWrapperView"]) {
if ([view isKindOfClass: [UIScrollView class]]) {
// turn OFF delaysContentTouches in the hidden subview
UIScrollView* scroll = (UIScrollView*)view;
scroll.delaysContentTouches = NO;
}
break;
}
}
}
return self;
}
#end
Next - use this subclass (create an instance of NoDelaysOnTouchTableView) to be able to press UIButton immediately.
I think that the causes would be multiples.
You can try:
check the constraints for each button in your cell
in cellForRow you can do addTarget: for each button with relative #selector and in this method check the object (datasource) associated to button.
do not use addSubView in your cell, instead use a Xib (or define your cell in a storyboard within tableView) so you can set the constraints.
I Hope, i've helped you.
Not specifically about this issue, but for some people it may be the case.Try to add subviews in contentView of the cell.
Instead of addSubview(textField) use contentView.addSubview(textField)
I am making an application that is a tab-view controller. For the first tab, I have a UIPageViewController that is scrolling through pictures. Everything works properly, except for that the picture is not showing full screen. I have constraints set up for the image view to cover the whole view controller but when it gets loaded into the page view, it doesn't cover all the way to the bottom. The image gets cut off by the scrolling dot indicators and then it is just white.
I'm sure it is simple, but is there a way that the images will cover the full screen like I have the constraints set up to do?
There's quite a simple solution for this problem on that page: http://tunesoftware.com/?p=3363
It is done by overriding the viewDidLayoutSubviews method of UIPageViewController and basically doing two things:
Increasing the bounds of the UIScrollView (the content) inside the UIPageViewController by setting it's frame to the bounds of the page controller's view
Moving the UIPageControl element (the dots) to the front of the view hierarchy so that it's in front of the content
Here's the code he provides (in case the link gets broken):
import UIKit
class TSPageViewController: UIPageViewController {
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
var subViews: NSArray = view.subviews
var scrollView: UIScrollView? = nil
var pageControl: UIPageControl? = nil
for view in subViews {
if view.isKindOfClass(UIScrollView) {
scrollView = view as? UIScrollView
}
else if view.isKindOfClass(UIPageControl) {
pageControl = view as? UIPageControl
}
}
if (scrollView != nil && pageControl != nil) {
scrollView?.frame = view.bounds
view.bringSubviewToFront(pageControl!)
}
}
}
All you have to do after that, is:
If you set your page controller in the storyboard, then just go to the identity inspector pane and change its class to TSPageViewController
If you set your page controller in code, then just make sure the class you defined for it inherits the TSPageViewController class.
Here's the Objective-C version. Create a new class (2 files):
(1) BasePageViewController.h extending UIPageViewController
#import <UIKit/UIKit.h>
#interface BasePageViewController : UIPageViewController
#end
(2) BasePageViewController.m containing viewDidLayoutSubviews override
#import "BasePageViewController.h"
#implementation BasePageViewController
- (void) viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
UIScrollView *scrollView = nil;
UIPageControl *pageControl = nil;
for(UIView *view in self.view.subviews)
{
if([view isKindOfClass:[UIScrollView class]])
{
scrollView = (UIScrollView *)view;
}else if([view isKindOfClass:[UIPageControl class]])
{
pageControl = (UIPageControl *)view;
}
}
if(scrollView != nil && pageControl!=nil)
{
scrollView.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
[self.view bringSubviewToFront:pageControl];
}
}
#end
I've created an extremely simple demo app to test the functionality of automaticallyAdjustsScrollViewInsets, but the last cell of the tableView is covered by my tab bar.
My AppDelegate code:
UITabBarController *tabControl = [[UITabBarController alloc] init];
tabControl.tabBar.translucent = YES;
testViewController *test = [[testViewController alloc] init];
[tabControl setViewControllers:#[test]];
[self.window setRootViewController:tabControl];
My testViewController (subclass of UITableViewController) Code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.automaticallyAdjustsScrollViewInsets = YES;
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
self.tableView.dataSource = self;
self.tableView.scrollIndicatorInsets = self.tableView.contentInset;
//[self.view addSubview:self.tableView];
// Do any additional setup after loading the view.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#""];
cell.textLabel.text = #"test";
return cell;
}
Is this a bug in iOS 7? If not, what did I do wrong?
I think that automaticallyAdjustsScrollViewInsets only works when your controllers view is a UIScrollView (a table view is one).
You're problem seems to be that your controller's view is a regular UIView and your UITableView is just a subview, so you'll have to either:
Make the table view the "root" view.
Adjust insets manually:
UIEdgeInsets insets = UIEdgeInsetsMake(controller.topLayoutGuide.length,
0.0,
controller.bottomLayoutGuide.length,
0.0);
scrollView.contentInset = insets;
Edit:
Seems like the SDK is capable of adjusting some scroll views despite not being the controller's root view.
So far It works with UIScrollView's and UIWebView's scrollView when they are the subview at index 0.
Anyway this may change in future iOS releases, so you're safer adjusting insets yourself.
For automaticallyAdjustsScrollViewInsets to work, your view controller must be directly on a UINavigationController's stack, i.e. not as a child view controller within another view controller.
If it is a child view controller of another view controller which is on the navigation stack, you can instead set automaticallyAdjustsScrollViewInsets = NO on the parent. Alternatively you can do this:
self.parentViewController.automaticallyAdjustsScrollViewInsets = NO;
I just solved this issue with iOS 11 and swift 4, my current problem was that iOS11 has a new property to validate the insets when a ScrollView does exist, that one is contentInsetAdjustmentBehavior which is a ScrollView's property and the default property is automatic so my code was:
if #available(iOS 11, *) {
myScroll.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
I hope this solve your problems too...
I have this hierarchy:
custom navigationcontroller contains custom tabbarcontroller
custom tabbarcontroller contains several controllers
these controllers contains subviews and one of them contains a subclass of uiscrollview.
I had to set automaticallyAdjustsScrollViewInsets to NO
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.automaticallyAdjustsScrollViewInsets = NO;
in the custom tabbarcontroller. Other controllers in the hierarchy do not have any impact on the nested scroll view's behavior.
I was having the same issue, a Table View with unwanted top padding.
All answers say to fix by setting automaticallyAdjustsScrollViewInsets = NO, but that was not eliminating the padding for me.
Similar to the other answers here, these directions need to be tweaked slightly if you're using a non-standard view hierarchy.
I had a UIViewController with an embedded UITableViewController. It was not working to set automaticallyAdjustsScrollViewInsets on the Table View Controller.
Instead, I set automaticallyAdjustsScrollViewInsets = NO on the parent UIViewController that was embedding my Table View Controller. That successfully eliminated the padding on the Table View.
I have in a controller:
- (void)viewDidLoad {
[super viewDidLoad];
// Add a scroolView
self.scroolViewDay.scrollEnabled = YES;
// Compute the content Size of the TableDays
self.scroolViewDay.contentSize = CGSizeMake(self.scroolViewDay.frame.size.width,
80 * 48); // TO MODIFY!
[self.scroolViewDay addSubview:self.tableDays];
[self.tableDays setNeedsDisplay];
}
The controller has a XIB where the UIScrollView is into.
The custom view TableDays has a custom drawRect which is never called:
- (void)drawRect:(CGRect)rect {
NSLog(#"sono in drawRect");
}
Why?
-(void) setNeedsDisplay {
[self.subviews makeObjectsPerformSelector:#selector(setNeedsDisplay)];
[super setNeedsDisplay];
}
Add this code, and just override setNeedsDisplay method in your main view and I hope that you know that all of your subviews should be redrawn.
Before adding it programmatically, add it from the storyboard with the correct constraints and see if it getting called.
In my case, there was a problem with the constraints I'm adding to this custom view.
This is the code with the problem,
let header = HeaderBackgroundView(frame: view.bounds)
scrollView.addSubview(header)
header.snp.makeConstraints { make in
make.leading.equalTo(scrollView.snp.leading)
make.trailing.equalTo(scrollView.snp.trailing)
make.top.equalTo(scrollView.snp.top)
make.height.equalTo(200)
}
i fixed it by adding the center x constraint:
header.snp.makeConstraints { make in
make.leading.equalTo(scrollView.snp.leading)
make.trailing.equalTo(scrollView.snp.trailing)
make.top.equalTo(scrollView.snp.top)
make.centerX.equalTo(scrollView.snp.centerX) // this line.
make.height.equalTo(200)
}
I have a scroll view that used to scroll when it didn't have buttons all over it. Now it does, and when dragging the mouse (on simulator) nothing happens (i think because the buttons are being pushed). How can I make this right?
This is happening because UIButton subviews of the UIScrollView (I assume buttons are added as subviews in your case) are tracking the touches and not the scroll view. UIScrollView method touchesShouldCancelInContentView is the key here. According to its description: "The default returned value is YES if view is not a UIControl object; otherwise, it returns NO.", i.e. for UIControl objects (buttons), UIScrollView does not attempt to cancel touches which prevents scrolling.
So, to allow scrolling with buttons:
Make sure UIScrollView property canCancelContentTouches is set to YES.
Subclass UIScrollView and override touchesShouldCancelInContentView to return YES when content view object is a UIButton, like this:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if ( [view isKindOfClass:[UIButton class]] ) {
return YES;
}
return [super touchesShouldCancelInContentView:view];
}
I founded this question looking for the swift solution for this problem, I "translated" it like this:
Swift 5
class UIButtonScrollView: UIScrollView {
override func touchesShouldCancel(in view: UIView) -> Bool {
if view.isKind(of: UIButton.self) {
return true
}
return super.touchesShouldCancel(in: view)
}
}
hope this could help
Swift 3 Solution
override func touchesShouldCancel(in view: UIView) -> Bool {
if view is UIButton {
return true
}
return super.touchesShouldCancel(in: view)
}
One thing to try if you're seeing this in a simulator is to run on an actual phone. I couldn't scroll in the simulator but no prob on my phone.
In my case, I solved it with this way.
in ViewDidLoad
self.scrollView.panGestureRecognizer.delaysTouchesBegan = self.scrollView.delaysContentTouches;
in .m
- (BOOL)touchesShouldCancelInContentView:(UIView *)view {
if ([view isKindOfClass:[UIControl class]]) return YES;
return NO;
}