I externalized a component in a XIB file so I can reuse it in several different views.
Now in my view controller I load the XIB-based UIView and add it to the current view through :
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"ThumbsPreviewView" owner:self options:nil];
_likePreview = [views objectAtIndex:0];
[self.view addSubview:_likePreview];
At first, I thought that it was not working... but actually it works but only put the root view (i.e. the container of all my graphical components, which is transparent) to my view.
So I need to put this kind of code (for every sub component) to make it work :
[self.view addSubview:_likePreview];
NSArray *subviews = _likePreview.subviews;
for(UIView * subview in subviews) {
[self.view addSubview:subview];
}
This is very counter-intuitive, and moreover I think it does not create the view hierarchy that I would like.
I am sure there is a solution because I did the same kind of thing in a UITableViewController to load a custom table header from a XIB. Using the exact same XIB file in this situation loads everything properly. Here is the code :
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
switch(section) {
case 0: {
NSArray *views = [[NSBundle mainBundle] loadNibNamed:#"ThumbsPreviewView" owner:self options:nil];
_likePreview = [views objectAtIndex:0];
return _likePreview;
}
}
return [super tableView:tableView viewForHeaderInSection:section];
}
With the above code, everything is fine, I got the whole view (= root view container with all its content) as the header of my table section.
So the question is : what am I doing wrong in my first example ? How can I add all content (container + all content) to my parent UIView as a proper UIView hierarchy ?
EDIT : I need to precise that everything is organized in my XIB file in a single root UIView, as follows (I cannot post image) :
* Thumbs preview view
|_ Button
|_ Image View
|_ Image View
|_ Image View
|_ Image View
|_ Button
|_ Button
|_ Button
|_ Label
Thank you very much for your help !
Christophe
One way to load your component is to store your components into a top UIView object (s. attached screenshot).
(source: mobypicture.com)
You can then load your nib as usual and use -addSubview: to add your components to the view hierarchy.
EDIT:
Loading the nib with
UIView *view = [[[NSBundle mainBundle] loadNibNamed:#"testView" owner:self options:nil] objectAtIndex:0];
and adding the view to the view hierarchy with
[self.view addSubview:view];
leads to the following view hiearchy:
(lldb) po [[self view] recursiveDescription]
(id) $1 = 0x088487b0 <UIView: 0x89595b0; frame = (0 0; 320 548); autoresize = W+H; layer = <CALayer: 0x8958b30>>
| <UIView: 0x89755c0; frame = (0 0; 320 200); autoresize = RM+BM; layer = <CALayer: 0x8975080>>
| | <UIView: 0x8975620; frame = (200 20; 100 30); autoresize = W+H; layer = <CALayer: 0x8975680>>
| | <UIView: 0x8975700; frame = (20 20; 100 30); autoresize = W+H; layer = <CALayer: 0x8974ea0>>
| | <UIRoundedRectButton: 0x8975790; frame = (20 58; 73 44); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8975890>>
| | | <UIGroupTableViewCellBackground: 0x8976030; frame = (0 0; 73 44); userInteractionEnabled = NO; layer = <CALayer: 0x8976100>>
| | | <UIImageView: 0x89768d0; frame = (1 1; 71 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x89770f0>>
| | | <UIButtonLabel: 0x8977af0; frame = (36 22; 0 0); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8977be0>>
| | <UIRoundedRectButton: 0x8978990; frame = (227 58; 73 44); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8978a60>>
| | | <UIGroupTableViewCellBackground: 0x8978a90; frame = (0 0; 73 44); userInteractionEnabled = NO; layer = <CALayer: 0x8978b10>>
| | | <UIImageView: 0x8978b80; frame = (1 1; 71 43); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8979230>>
| | | <UIButtonLabel: 0x8978be0; frame = (36 22; 0 0); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8978cd0>>
Related
The view that is presented as a subview of another view has the following setup in a .Xib file.
Here is how the view is presented when the button to present it is touched.
Here is the code for how the view is presented.
-(void)setUpImageSelectorView{
ImageSelectorViewViewController *imageSelectorVC = [[ImageSelectorViewViewController alloc]initWithNibName:#"ImageSelectorViewViewController" bundle:nil];
imageSelectorVC.delegate = self;
imageSelectorVC.view.contentMode = UIViewContentModeScaleAspectFit;
imageSelectorVC.view.clipsToBounds = YES;
imageSelectorVC.imageView.image = img;
[self.view addSubview:imageSelectorVC.view];
}
Two issues:
This is not the correct code to add a subview that has its own controller. You should use custom container calls, e.g.:
-(void)setUpImageSelectorView{
ImageSelectorViewViewController *imageSelectorVC = [[ImageSelectorViewViewController alloc]initWithNibName:#"ImageSelectorViewViewController" bundle:nil];
[self addChildViewController:imageSelectorVC];
imageSelectorVC.delegate = self;
imageSelectorVC.view.contentMode = UIViewContentModeScaleAspectFit;
imageSelectorVC.view.clipsToBounds = YES;
imageSelectorVC.imageView.image = img;
[self.view addSubview:imageSelectorVC.view];
// you probably should set the `frame` of this view, too
[imageSelectorVC didMoveToParentViewController:self];
}
Note the addChildViewController and didMoveToParentViewController calls.
When you go to remove it, first call [imageSelectorVC willMoveToParentViewController:nil] and when when you're done removing everything, call [self removeChildViewController: imageSelectorVC].
See Creating Custom Container View Controllers in the View Controller Programming Guide for iOS. For a discussion of why all of this is so important (namely, keeping your view hierarchy synchronized with your view controller hierarchy) see WWDC 2011 video Implementing UIViewController Containment.
If you run the app through the debugger and pause the execution (hit the pause button, not the stop button), you can confirm the layout of the views by typing
(lldb) po [[UIWindow keyWindow] recursiveDescription]
<UIWindow: 0x7fd91b542420; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7fd91b543000>; layer = <UIWindowLayer: 0x7fd91b5412d0>>
| <UIView: 0x7fd91b5442d0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fd91b5435c0>>
| | <_UILayoutGuide: 0x7fd91b5445e0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7fd91b537030>>
| | <_UILayoutGuide: 0x7fd91b544f40; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7fd91b543850>>
| | <UIButton: 0x7fd91b446d50; frame = (5 285; 70 30); opaque = NO; tag = 12221; layer = <CALayer: 0x7fd91b4d8fb0>>
| | | <UIImageView: 0x7fd91b57aa20; frame = (0 0; 70 30); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd91b53b6f0>>
| | | <UIImageView: 0x7fd91b51c040; frame = (0 0; 0 0); clipsToBounds = YES; hidden = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x7fd91b539670>>
Sometimes views are there, but you won't see it if their width/height is zero (especially if you are clipping to the bounds).
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.