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.
Related
I'm trying to show a simple settings screen in my app and I'm using Autolayout. Since my app supports landscape, and the settings controls don't quite fit vertically when the phone is landscape, I added a UIScrollview.
It seems that the combination of UIScrollview and Autolayout is a common question here and elsewhere. I've read a few articles on it, and from what it can tell, it seems that a good way to go for what I'm trying to do is to put my various controls within a view, like a "contentView" with constraints aligned in that view. Then have that contentView be the sole subview of the UIScrollView. I have the contentView with constraints tied to the edges of the UIScrollView.
UINavigationView -> UIScrollview -> contentView (UIView) -> Control Subviews
It works fine in portrait, even if I add content in my contentView that requires scrolling to see, but when I go to landscape, it doesn't let me scroll down far enough. It's like the content size gets reset to the frame of the visible area (or the frame of the navigation controller superview) when I rotate.
I'm wondering if I need to check for rotation and then set the content size again at that time? If so, is there a way to get that size dynamically from the view (contentView)?
Thanks for any help you can offer!
Jim
There are two components to having auto layout properly handle scroll views:
Constraints for scroll view's subviews: The contentSize of the scroll view will be dictated by the constraints of the scroll view's subviews. Thus, you need to have a bottom constraint for the last control in the scroll view (i.e. second switch) to its superview (i.e. the scroll view). It looks like you do have this. These constraints for subviews of the scroll view will automatically adjust the scroll view's contentSize as needed.
By the way, the constant for this second switch's bottom constraint will generally default to some largish value corresponding to how it was laid out in portrait. You may want to select and edit this bottom constraint for this last control and change it so that it's the "standard" value.
Constraints for the scroll view itself: You need to make sure you have the bottom constraint for the scroll view, itself, to its superview (and make sure you do not have a height constraint on the scroll view). This will adjust the frame of the scroll view upon screen rotation. I wonder if this might be missing in your project.
You can confirm this by running your app in the debugger, hitting "pause" button:
and then at the (lldb) prompt, type po [[UIWindow keyWindow] recursiveDescription], and you should see something like:
(lldb) po [[UIWindow keyWindow] recursiveDescription]
<UIWindow: 0x8b97ff0; frame = (0 0; 320 480); autoresize = W+H; gestureRecognizers = <NSArray: 0x8b98450>; layer = <UIWindowLayer: 0x8b97af0>>
| <UIView: 0x8b9a830; frame = (0 0; 320 480); transform = [0, -1, 1, 0, 0, 0]; autoresize = RM+BM; layer = <CALayer: 0x8b9a240>>
| | <UIScrollView: 0x8b9aa50; frame = (0 0; 480 320); clipsToBounds = YES; autoresize = RM+TM; gestureRecognizers = <NSArray: 0x8b9a6c0>; layer = <CALayer: 0x8b9a130>; contentOffset: {0, 0}>
| | | <UILabel: 0x8b9ade0; frame = (20 20; 63 21); text = 'Settings'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9aec0>>
| | | <UILabel: 0x8b9d590; frame = (20 49; 51 21); text = 'View 1'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9d6b0>>
| | | <UILabel: 0x8b9db70; frame = (408 49; 51 21); text = 'View 2'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9dc10>>
| | | <UIView: 0x8b9de60; frame = (335 78; 124 53); autoresize = RM+BM; layer = <CALayer: 0x8b9dec0>>
| | | <UIView: 0x8b9e000; frame = (20 78; 284 53); autoresize = RM+BM; layer = <CALayer: 0x8b9e060>>
| | | <UILabel: 0x8b9e250; frame = (20 139; 59 21); text = 'Slider 1'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9e2f0>>
| | | <UISlider: 0x8b9e460; frame = (18 238; 444 34); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b9e5c0>; value: 0.500000>
| | | | <UIView: 0x8ace410; frame = (2 16; 440 2); userInteractionEnabled = NO; layer = <CALayer: 0x8ace470>>
| | | | | <UIView: 0x8ace610; frame = (221 0; 219 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace670>>
| | | | | | <UIView: 0x8ace6e0; frame = (-221 0; 440 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace740>>
| | | | | | | <CAGradientLayer: 0x8ace7b0> (layer)
| | | | | <UIView: 0x8ace7e0; frame = (0 0; 221 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace840>>
| | | | <UIImageView: 0x8ace9d0; frame = (207 1; 31 31); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acea60>>
| | | | | <UIImageView: 0x8ace8b0; frame = (-13 -6.5; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace940>>
| | | <UISlider: 0x8b9e850; frame = (17 168; 444 34); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b9e930>; value: 0.500000>
| | | | <UIView: 0x8ba5430; frame = (2 16; 440 2); userInteractionEnabled = NO; layer = <CALayer: 0x8ba5490>>
| | | | | <UIView: 0x8acc190; frame = (221 0; 219 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acae30>>
| | | | | | <UIView: 0x8acc3f0; frame = (-221 0; 440 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ac84d0>>
| | | | | | | <CAGradientLayer: 0x8accb90> (layer)
| | | | | <UIView: 0x8acdf50; frame = (0 0; 221 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acdfb0>>
| | | | <UIImageView: 0x8ace070; frame = (207 1; 31 31); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace100>>
| | | | | <UIImageView: 0x8acdfe0; frame = (-13 -6.5; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acdc80>>
| | | <UILabel: 0x8b9e9c0; frame = (20 209; 59 21); text = 'Slider 2'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9ea60>>
| | | <UISwitch: 0x8b9ece0; frame = (411 279; 51 31); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b9edd0>>
| | | | <_UISwitchInternalViewNeueStyle1: 0x8b9f0c0; frame = (0 0; 51 31); gestureRecognizers = <NSArray: 0x8b723f0>; layer = <CALayer: 0x8b9f1c0>>
| | | | | <UIView: 0x8b9f750; frame = (35.5 0; 15.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b9f7b0>>
| | | | | | <UIView: 0x8b9f3b0; frame = (-35.5 0; 51 31); layer = <CALayer: 0x8b9f410>>
| | | | | <UIView: 0x8b9f6c0; frame = (0 0; 35.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b9f720>>
| | | | | | <UIView: 0x8b9f440; frame = (0 0; 51 31); layer = <CALayer: 0x8b9f4a0>>
| | | | | <UIView: 0x8ba11e0; frame = (0 0; 51 31); layer = <CALayer: 0x8ba1240>>
| | | | | | <UIImageView: 0x8ba0f10; frame = (39 16; 0 0); alpha = 0; userInteractionEnabled = NO; layer = <CALayer: 0x8ba10f0>>
| | | | | | <UIImageView: 0x8ba1120; frame = (12 16; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x8ba11b0>>
| | | | | <UIImageView: 0x8b9fab0; frame = (7 -6; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ba0a40>>
| | | <UISwitch: 0x8b974b0; frame = (411 318; 51 31); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b75570>>
| | | | <_UISwitchInternalViewNeueStyle1: 0x8b7d6a0; frame = (0 0; 51 31); gestureRecognizers = <NSArray: 0x8b845e0>; layer = <CALayer: 0x8b78750>>
| | | | | <UIView: 0x8b7df70; frame = (35.5 0; 15.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b7be00>>
| | | | | | <UIView: 0x8b72410; frame = (-35.5 0; 51 31); layer = <CALayer: 0x8b7a660>>
| | | | | <UIView: 0x8b7ba80; frame = (0 0; 35.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b7bd00>>
| | | | | | <UIView: 0x8b78dd0; frame = (0 0; 51 31); layer = <CALayer: 0x8b7ba20>>
| | | | | <UIView: 0x8b81580; frame = (0 0; 51 31); layer = <CALayer: 0x8b77ee0>>
| | | | | | <UIImageView: 0x8b97880; frame = (39 16; 0 0); alpha = 0; userInteractionEnabled = NO; layer = <CALayer: 0x8b7e1a0>>
| | | | | | <UIImageView: 0x8b77460; frame = (12 16; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x8b7e350>>
| | | | | <UIImageView: 0x8b80670; frame = (7 -6; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8b80700>>
| | | <UILabel: 0x8b97cf0; frame = (335 284; 67 21); text = 'Switch 1'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b808b0>>
| | | <UILabel: 0x8b79d60; frame = (335 323; 67 21); text = 'Switch 2'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b77b90>>
| | | <UIImageView: 0x8ac44f0; frame = (476 473; 3 7); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x8a89130>>
| | | <UIImageView: 0x8ac2be0; frame = (313 316; 7 3); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x8ac2c70>>
| | <_UILayoutGuide: 0x8b9cb20; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x8b9e5f0>>
| | <_UILayoutGuide: 0x8ac4440; frame = (0 320; 0 0); hidden = YES; layer = <CALayer: 0x8a90370>>
Check the frame of the UIScrollView (e.g. the above is for landscape on iPhone), and make sure it corresponds to the size of screen. If you haven't defined the constraints between the scroll view and its superview, this frame may be incorrect.
Maybe others will have a better solution for this, but the fastest without changing too much on your current build would be to do exactly what you said: check for rotation and then set the content size again.
It would be done like this in your Settings View Controller implementation file:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
self.scrollView.contentSize = self.contentView.bounds.size;
}
... assuming you have added in that same file outlets to your UIScrollView and your UIView named scrollView and contentView respectively.
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 ;]
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
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.
I came across this while debugging why my scrollView wouldn't scroll. For context, the structure of my layout is:
-ScrollView
- View
- labels, images, buttons
Updating the scrollView.contentSize to match the (sub)view wasn't working, so I put in some debug statements. Turns out the view's frame is sized 0, 0 when i NSLog its description, but if i run the debugger's recursiveDescription, it shows different.
Any idea why, and could that be affecting why my scrolling isn't working?
From NSLog(#"%#", [self.view description])
2012-11-15 17:16:46.707 Comparo[65731:11303] starting: <UIView:
0x7144590; frame = (0 0; 0 0); autoresize = TM+BM; layer =
<CALayer: 0x714b4f0>>
2012-11-15 17:16:46.708 Comparo[65731:11303] 1: <UIView: 0x7144590;
frame = (0 0; 0 0); autoresize = TM+BM; layer = <CALayer:
0x714b4f0>>
2012-11-15 17:16:46.771 Comparo[65731:11303] 2: <UIView: 0x7144590;
frame = (0 0; 0 0); autoresize = TM+BM; layer = <CALayer:
0x714b4f0>>
2012-11-15 17:16:46.771 Comparo[65731:11303] 3: <UIView: 0x7144590;
frame = (0 0; 0 0); autoresize = TM+BM; layer = <CALayer:
0x714b4f0>>
2012-11-15 17:16:46.772 Comparo[65731:11303] 4: <UIView: 0x7144590;
frame = (0 0; 0 0); autoresize = TM+BM; layer = <CALayer:
0x714b4f0>>
Debugger output for: (lldb) po [[UIApp keyWindow] recursiveDescription]
...
| | | | <UIScrollView: 0x71494d0; frame = (0 0; 320 504);
clipsToBounds = YES; autoresize = TM+BM; gestureRecognizers =
<NSArray: 0x75d3bb0>; layer = <CALayer: 0x71f9ed0>;
contentOffset: {0, 0}>
| | | | | <UIView: 0x7144590; frame = (0 0; 320 523);
autoresize = TM+BM; layer = <CALayer: 0x714b4f0>>
Thanks.
Thanks Rob!
You were right about it being a timing issue and there's no way to guarantee that frames will be finalized in viewDidLoad (where I had my NSLog statements). I moved the statements to viewDidAppear:, which is where the user will first be able to interact with them and scroll anyway, and the numbers match up now.
Bonus: solved my scrolling issue too.