This is so bizarre that I created a new project to test if I was going mad.
You can see the project here https://github.com/ojfoggin/TapTest
I've created a project with a UITabBarController As the initial view.
The first controller tab has two UIimageViews. Each image view has a UItapGestureRecognizer on it with an action that just logs "Tap 1" or "Tap 2".
If the TabBarController isn't used then everything works fine. However if the TabBarController is used then only the first tap recognizer works, the second doesn't.
However, if you switch t a different tab and then back again then both recognisers work?!?!?
Also, I have tried adding the Tao gesture recognisers in code and exactly the same thing happens.
Can anyone explain why this is happening and how to fix it?
TL;DR
Open your storyboard file as source code (xml) and replace
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
with
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES/>
For the view controller with the images.
Explanation
The problem is pretty easy to find with common debugging tools:
(lldb) po [[[[UIApplication sharedApplication] delegate] window] recursiveDescription]
shows
<UIWindow: 0x8c6a140; frame = (0 0; 320 480); autoresize = W+H; gestureRecognizers = <NSArray: 0x8c6a820>; layer = <UIWindowLayer: 0x8c66db0>>
| <UILayoutContainerView: 0x8c6a320; frame = (0 0; 320 480); transform = [0, -1, 1, 0, 0, 0]; autoresize = W+H; layer = <CALayer: 0x8c62350>>
| | <UITransitionView: 0x8c6a880; frame = (0 0; 480 320); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0x8c50240>>
| | | <UIViewControllerWrapperView: 0x8c6cd70; frame = (0 0; 320 480); autoresize = RM+BM; layer = <CALayer: 0x8c6c840>>
| | | | <UIView: 0x8c51070; frame = (0 0; 480 271); clipsToBounds = YES; autoresize = RM+BM; autoresizesSubviews = NO; layer = <CALayer: 0x8c51d80>>
| | | | | <UIImageView: 0x8c6bdc0; frame = (20 0; 219 160); autoresize = W+H; gestureRecognizers = <NSArray: 0x8c65180>; layer = <CALayer: 0x8c4c930>>
| | | | | <UIImageView: 0x8c6a780; frame = (247 0; 219 160); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x8c62a70>; layer = <CALayer: 0x8c6a9a0>>
| | | | | <_UILayoutGuide: 0x8c6c340; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x8c51110>>
| | | | | <_UILayoutGuide: 0x8c6c760; frame = (0 271; 0 0); hidden = YES; layer = <CALayer: 0x8c6c7d0>>
| | <UITabBar: 0x8c64180; frame = (0 271; 480 49); autoresize = W+TM; layer = <CALayer: 0x8c642a0>>
| | | <_UITabBarBackgroundView: 0x8a48e50; frame = (0 0; 480 49); autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x8a48f40>>
| | | <UITabBarButton: 0x8c64980; frame = (2 1; 236 48); opaque = NO; layer = <CALayer: 0x8c684c0>>
| | | | <UITabBarButtonLabel: 0x8c64dd0; frame = (108 35; 21 12); text = 'Item'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8c64ef0>>
| | | <UITabBarButton: 0x8c69d90; frame = (242 1; 236 48); opaque = NO; layer = <CALayer: 0x8c6a250>>
| | | | <UITabBarButtonLabel: 0x8c69e70; frame = (108 35; 21 12); text = 'Item'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8c68c20>>
| | | <UIImageView: 0x8a49240; frame = (0 -0.5; 480 0.5); autoresize = W; userInteractionEnabled = NO; layer = <CALayer: 0x8a492d0>>
where the important part is:
<UILayoutContainerView: 0x8c6a320; frame = (0 0; 320 480); autoresize = W+H;
| | <UITransitionView: 0x8c6a880; frame = (0 0; 480 320);autoresize = W+H
| | | <UIViewControllerWrapperView: 0x8c6cd70; frame = (0 0; 320 480); autoresize = RM+BM;
| | | | <UIView: 0x8c51070; frame = (0 0; 480 271); autoresize = RM+BM
Note that the UIViewControllerWrapperView has a bad size because its autoresizing mask is not W+H but RM+BM. The root cause however is the autoresizing mask of the UIView because UIViewControllerWrapperView is generated dynamically and the mask is only copied.
If you inspect the source code of the storyboard, you will see this line:
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
For the view in the first view controller.
Replacing it with
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
will fix the problem.
#Desdenova: rdurand is absolutely right. I did put some log statements in the view did load and view will appear and here is what it looks like.
2014-02-14 09:04:19.167 TapTest[3395:60b] The frame is {{0, 0}, {320, 568}}
2014-02-14 09:04:19.174 TapTest[3395:60b] The frame in viewwillappear is {{0, 0}, {320, 519}}
2014-02-14 09:04:27.814 TapTest[3395:60b] The frame in viewwillappear is {{0, 0}, {568, 271}}
The very first time in viewDidload and viewwillappear, the frame is 320,568, but when you go to the second view and come back, the frame is set properly 568,271. So the solution would be setting the frame right while the view controllers are added to the tabbarcontroller
I think this comes from your app changing orientation. If you launch the app and tap on the left side (about the first third of the image) of the second UIImageView, it logs "Tap 2" correctly. I think what happens is that your app launches in portrait, switches to landscape, and messes your gesture recognizer's frame of action. When you leave and come back to the tab, the app is already in landscape, so the frame is updated correctly.
Solution : not a single clue. I only work on portrait apps, so I don't really know what happens with this orientation. That being said, the origin of the problem might not be exactly what I said. But I'd say it's something to look into.
Edit :
I logged the tap gesture x position in the main view :
NSLog(#"%f", [((UITapGestureRecognizer*)sender) locationInView:self.view].x);
The maximum x position where the gesture is recognized seems to be… 320, which is the width of a portrait app. This seems to confirm my idea of the orientation doing something to your views..
Edit 2 : Solution !
I found this thread : Landscape tab bar.
The issue comes with a UITabBarController in landscape mode. Just add the following in your viewDidLoad :
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
I tested it and it works fine now. Remember to upvote Josh Adams' answer as it solves your problem ;]
Related
I've disabled autolayout from xib. But I'm facing this issue. Even I'm not adding any subview programmatically, and I'm having 3 element in this view:
1. UIImageView 2. UIButton 3. UIButton.
I'm suffering for the last 3 days but I'm not able to find out the error. So please any one help me. Your help will be appreciated.
NSLog(#"Constratins %#",[self.view constraints]);
Constratins (
)
View hierarchy unprepared for constraint.
Constraint: <NSAutoresizingMaskLayoutConstraint:0x294d9de0 h=-&- v=-&- UIViewControllerWrapperView:0x2d91c410.midY == UINavigationTransitionView:0xdd2aa40.midY>
Container hierarchy:
<UINavigationTransitionView: 0xdd2aa40; frame = (0 0; 1024 768); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0xdd2aca0>>
| <UIViewControllerWrapperView: 0x2a72b4d0; frame = (0 0; 1024 768); layer = <CALayer: 0x2a72ab40>>
| | <UIView: 0x2d869b30; frame = (0 0; 1024 768); gestureRecognizers = <NSArray: 0x2d8852e0>; layer = <CALayer: 0x2d86b510>>
| | | <MyImageView: 0x2d891040; baseClass = UIImageView; frame = (0 0; 1024 748); userInteractionEnabled = NO; layer = <CALayer: 0x2d8699f0>> - (null)
| | | <UIButton: 0x2d88c460; frame = (968 14; 40 40); hidden = YES; opaque = NO; layer = <CALayer: 0x2d88c550>>
| | | <UIButton: 0x2d88a220; frame = (963 9; 40 44); opaque = NO; layer = <CALayer: 0x2d88a310>>
View not found in container hierarchy: <UIViewControllerWrapperView: 0x2d91c410; frame = (0 0; 1024 768); autoresize = W+H; layer = <CALayer: 0x2d91c490>>
That view's superview: NO SUPERVIEW
Yes, I found the solution. The reason is : not setting the frame of UIViewControllerWrapperView
So i've do this
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
self.tabBarController.view.superview.frame = self.view.bounds;
}
My app uses a UINavigationController, but I do not show the toolbar because all of my navigation is controlled by in-game controls.
I do not have a Tab bar, because I'm not using a TabBarController.
My game app is Landscape only.
This is how I previously created and presented the UIActionSheet:
UIActionSheet *quitGameSheet = [[UIActionSheet alloc]
initWithTitle:#"Quit your game?"
delegate:self
cancelButtonTitle:#"Cancel"
destructiveButtonTitle:#"Yes, quit game"
otherButtonTitles:nil];
[quitGameSheet showInView:self.view];
However, the UIActionSheet is mostly off screen and I get this error:
Presenting action sheet clipped by its superview. Some controls might not respond to touches. On iPhone try -[UIActionSheet showFromTabBar:] or -[UIActionSheet showFromToolbar:] instead of -[UIActionSheet showInView:].
However, I do not have a toolbar and I do not have a tabbar.
I've tried several other Stack Overflow answers, none of which work for me:
Issue with UIActionSheet
I tried presenting it from self.navigationController.view, from self.parentViewController.view, from a CGRect that I created which was at the bottom of the view, from self.view.bounds because I was desperate.. none of it works for me.
It works in iOS 6, but does not work in iOS 7. Here are a couple of screenshots of it working on iOS 6 and failing on iOS 7.
Any help?
UPDATE 1 ---
Here is a view hierarchy as requested:
<UIView: 0xc480380; frame = (0 0; 568 320); autoresize = RM+BM; layer = <CALayer: 0xc4803e0>>
| <UIImageView: 0xc480410; frame = (0 0; 568 320); opaque = NO; autoresize = W+H; userInteractionEnabled = NO; layer = <CALayer: 0xc4804a0>>
| <UIButton: 0xc47b370; frame = (203 107; 159 37); opaque = NO; autoresize = LM+RM+BM; layer = <CALayer: 0xc47a730>>
| | <UIImageView: 0xc475150; frame = (0 0; 159 37); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xc4751e0>>
| <UIButton: 0xc478a00; frame = (203 152; 159 37); opaque = NO; autoresize = LM+RM+BM; layer = <CALayer: 0xc477dd0>>
| | <UIImageView: 0xc475030; frame = (0 0; 159 37); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xc4750c0>>
| <UISwitch: 0xc47dcc0; frame = (313 65; 51 31); opaque = NO; autoresize = LM+RM+BM; layer = <CALayer: 0xc47d180>>
| | <_UISwitchInternalViewNeueStyle1: 0xc47e040; frame = (0 0; 51 31); gestureRecognizers = <NSArray: 0xc47ffa0>; layer = <CALayer: 0xc47e140>>
| | | <UIView: 0xc47e4e0; frame = (35.5 0; 15.5 31); clipsToBounds = YES; layer = <CALayer: 0xc47e540>>
| | | | <UIView: 0xc47e330; frame = (-35.5 0; 51 31); layer = <CALayer: 0xc47e390>>
| | | <UIView: 0xc47e450; frame = (0 0; 35.5 31); clipsToBounds = YES; layer = <CALayer: 0xc47e4b0>>
| | | | <UIView: 0xc47e3c0; frame = (0 0; 51 31); layer = <CALayer: 0xc47e420>>
| | | <UIView: 0xc47f750; frame = (0 0; 51 31); layer = <CALayer: 0xc47f7b0>>
| | | | <UIImageView: 0xc47f480; frame = (39 16; 0 0); alpha = 0; userInteractionEnabled = NO; layer = <CALayer: 0xc47f660>>
| | | | <UIImageView: 0xc47f690; frame = (12 16; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0xc47f720>>
| | | <UIImageView: 0xc47e680; frame = (7 -6; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xc47f2f0>>
| <UILabel: 0xc480610; frame = (173 63; 96 29); text = 'Sounds:'; clipsToBounds = YES; opaque = NO; autoresize = LM+RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0xc480540>>
| <UIButton: 0xc473a20; frame = (203 197; 159 37); opaque = NO; autoresize = LM+RM+BM; layer = <CALayer: 0xc473590>>
| | <UIImageView: 0xc474f30; frame = (0 0; 159 37); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0xc474fc0>>
I had what appeared to be a completely unrelated problem in the same app, wherein some of the buttons on the left side of the screen weren't tappable.
Can't tap buttons on the left side of 4" iPhone
While investigating that I discovered that my UIWindow was 320x480. The problem was in my MainWindow.xib. Xcode created this xib for me, but it was probably Xcode 3 and it was several years ago. I decided to review the settings and I noticed the option for "Full Screen at Launch." I looked up that option in the docs and there was a note stating that this should always be on so that the Window is properly resized to match the device size. This was never an issue when there was only one size iPhone, but it became a problem with the introduction of the 4" iPhone.
So, I checked that box, and relaunched and viola, both this and the other problem were solved because the enclosing UIWindow was correctly sized.
I have recently solved a similar issue happening on iOS 7 (although it did not involve an action sheet) by presenting in the main window; in your case it would be something like:
[quitGameSheet showInView:[UIApplication sharedApplication].keyWindow];
hope it will work in your case as well.
[sheet showInView:[UIApplication sharedApplication].keyWindow];
sheet.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height-
sheet.frame.size.height, [UIScreen mainScreen].bounds.size.width,
sheet.frame.size.height);
Link: Issue with UIActionSheet
Form: user352537
I have noticed some strange behaviour for a UIScrollView.
In a new XCode(4.6.2 on OS X 10.8.4) iOS project, I've only added a RootViewController, with one line of custom code: adding a UIScrollView as a subview
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view addSubview:[[UIScrollView alloc] initWithFrame:self.view.bounds]];
}
Then after touching the screen once, when I inspect the view hierarchy in lldb, the output is as follows.
(lldb) po [[UIWindow keyWindow] recursiveDescription]
$0 = 0x07126f10 <UIWindow: 0x7528660; frame = (0 0; 320 568); layer = <UIWindowLayer: 0x7528730>>
| <UIView: 0x7668580; frame = (0 20; 320 548); autoresize = W+H; layer = <CALayer: 0x7668910>>
| | <UIScrollView: 0x7666200; frame = (0 0; 320 548); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x7666df0>; layer = <CALayer: 0x76624f0>; contentOffset: {0, 0}>
| | | <UIImageView: 0x11121a30; frame = (313 541; 7 7); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x11121b60>> - (null)
| | | <UIImageView: 0x11121bd0; frame = (313 541; 7 7); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x11121c70>> - (null)
Suddenly the UIScrollView has two subviews which I did not add myself. This happens both in the simulator and on the device (also both iPhone and iPad).
So my question is:
Is this normal behaviour? If so, why would it be?
They are the scroll indicators. One UIImageView for the horizontal indicator and another one for the vertical indicator.
It's probably the scroll indicators. These details shouldn't really matter, since you should be using the scrollview's .contentView to hold your own subviews.
An UIButton is contained inside a UIScrollView. I have this setup in a xib. I use a NSLayoutConstraint to change the height of the scrollview. After I click the button, the scrollview changes height, but then UIButton becomes unclickable.
Here is the code:
- (IBAction)TagPressed:(UIButton *)sender {
if (self.height.constant == 200) {
self.height.constant = 88;
}else
self.height.constant = 200;
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.5f animations:^{
[self.view layoutIfNeeded];
}];
DRect(_scrollView.frame)
DRect(self.view.frame)
DRect(sender.frame)
NSLog(sender.selected ? #"Selected" : #"Not Selected");
}
Drect is a nslog for frames. So the Console out is:
2013-04-08 17:04:19.264 TouchSelectApp[93618:c07] CGRect ( 20.000000, -112.000000, 62.000000, 200.000000)
2013-04-08 17:04:19.266 TouchSelectApp[93618:c07] CGRect ( 0.000000, 300.000000, 320.000000, 88.000000)
2013-04-08 17:04:19.266 TouchSelectApp[93618:c07] CGRect ( 7.000000, 78.500000, 48.000000, 44.000000)
Output from (lldb) po [[UIApp keyWindow] recursiveDescription]
I had to add a an extra button to create a debugging stop. The button I care about is inside SelectableTag.
$0 = 0x0887e0e0 <UIWindow: 0x7197a90; frame = (0 0; 320 568); autoresize = W+H; layer = <UIWindowLayer: 0x7199620>>
| <UIView: 0x719ed20; frame = (0 20; 320 548); autoresize = W+H; layer = <CALayer: 0x719edd0>>
| | <UIRoundedRectButton: 0x719b9d0; frame = (51 464; 73 44); opaque = NO; autoresize = TM+BM; layer = <CALayer: 0x719baf0>>
| | | <UIGroupTableViewCellBackground: 0x719c2a0; frame = (0 0; 73 44); userInteractionEnabled = NO; layer = <CALayer: 0x719c370>>
| | | <UIImageView: 0x719da50; frame = (1 1; 71 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x719dd20>>
| | | <UIButtonLabel: 0x719d0f0; frame = (12 12; 49 19); text = 'Button'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x719d1e0>>
| | <UIView: 0x71a1160; frame = (0 300; 320 88); autoresize = RM+BM; layer = <CALayer: 0x71a11c0>>
| | | <SelectableTag: 0x719fcc0; baseClass = UIScrollView; frame = (20 -112; 62 200); clipsToBounds = YES; autoresize = W+H; gestureRecognizers = <NSArray: 0x71a04e0>; layer = <CALayer: 0x719fef0>; contentOffset: {0, 0}>
| | | | <UIImageView: 0x71a0c20; frame = (55 193; 7 7); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x71a0cc0>>
| | | | <UIImageView: 0x71a0dd0; frame = (55 62; 7 7); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x71a0e70>>
| | | | <UIRoundedRectButton: 0x719ddd0; frame = (-1 7; 64 44); opaque = NO; layer = <CALayer: 0x719b630>>
| | | | | <UIGroupTableViewCellBackground: 0x719d630; frame = (0 0; 64 44); userInteractionEnabled = NO; layer = <CALayer: 0x719b690>>
| | | | | <UIImageView: 0x719dea0; frame = (1 1; 62 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x719d990>>
| | | | | <UIButtonLabel: 0x71a1630; frame = (12 12; 40 19); text = 'Test1'; clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x71a1e50>>
(lldb)
From the documentation of hitTest:withEvent: in the UIView Class Reference:
Points that lie outside the receiver’s bounds are never reported as hits, even if they actually lie within one of the receiver’s subviews. This can occur if the current view’s clipsToBounds property is set to NO and the affected subview extends beyond the view’s bounds.
The parent of your scroll view is the view with address 0x71a1160. Notice that the scroll view's frame.origin.y is -112. So the top 112 points of the scroll view are outside of its parent's bounds. That part of the scroll view will never receive touches. Your Test1 button is entirely in that part of the scroll view.
I have noticed this behavior too with UIButtons. After changing the frame of the parent view, if you add the button again it will work.
My question is, is the default iPhone menu a UIcollection view created with storyboarding, or is there more to its abilities? (in order to avoid emulating its functionality)
If you want to understand SpringBoard's view hierarchy:
Launch the iOS Simulator if it's not already running.
Make sure you're looking at SpringBoard in the simulator.
In Xcode, choose Product > Attach to Process > springboard. The springboard process is way down in the “System” section of the list.
Still in Xcode, choose Product > Debug > Pause.
In Xcode's debug console (where it should show the (lldb) prompt), type po [[UIApp keyWindow] recursiveDescription].
Voila, you get a printout of SpringBoard's entire on-screen view hierarchy. Mine starts out like this:
(lldb) po [[UIApp keyWindow] recursiveDescription]
$0 = 0x0b63abf0 <SBAppWindow: 0xce3ef10; baseClass = UIWindow; frame = (0 0; 320 480); layer = <UIWindowLayer: 0xce3f010>>
| <SBUIRootView: 0xb162de0; frame = (0 0; 320 480); layer = <CALayer: 0xb162e60>>
| | <UIView: 0xb163010; frame = (0 0; 320 480); autoresize = W+H; layer = <CALayer: 0xb1630c0>>
| | | <SBWallpaperView: 0xb1632a0; baseClass = UIImageView; frame = (0 0; 320 480); userInteractionEnabled = NO; layer = <CALayer: 0xb160f70>>
| | | | <UIImageView: 0xb1610d0; frame = (0 0; 0 0); hidden = YES; userInteractionEnabled = NO; layer = <CALayer: 0xb161130>>
| | | | <UIImageView: 0xb161160; frame = (0 0; 0 0); hidden = YES; userInteractionEnabled = NO; layer = <CALayer: 0xb163390>>
| | | <SBIconContentView: 0xb163da0; frame = (0 0; 320 480); clipsToBounds = YES; autoresize = W+H; layer = <CALayer: 0xb163e50>>
| | | | <UIView: 0xc939710; frame = (0 371; 320 109); opaque = NO; autoresize = W+TM; layer = <CALayer: 0xc939770>>
| | | | | <SBDockIconListView: 0xc9381d0; frame = (0 20; 320 89); autoresize = TM; layer = <CALayer: 0xc9382c0>>
...
From Wikipedia article on Springboard:
Springboard, or Home Screen is the standard application that manages the iOS home screen. Other tasks include starting WindowServer, launching and bootstrapping applications and setting some of the device's settings on startup.