Where to set popover size? - objective-c

I have a UIViewController called HomeViewController and it has a model that contains an array of data. HomeViewController also has a button that when pressed will show a UITableViewController that display's the model's array of data.
My question is, what is the standard/best way to set the popover's size? I only want the popover to be tall enough to display the data, but no taller. What is common practice? I assume that I need to set contentSizeForViewInPopover at some point but I'm not sure where...In the viewDidLoad method of the popover class? In the prepareForSegue method?
Here's the code I have so far: (Note that DataPresentingViewController is my popover view controller class`)
//In HomeViewController
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.destinationViewController isKindOfClass:[DataPresentingViewController class]])
{
DataPresentingViewController *dest = (DataPresentingViewController *) segue.destinationViewController;
dest.mySavedData = self.myModel.mySavedData;
}
}
I know that I could just set the contentSizeForViewInPopover here in the prepareForSegue method; however, this seems like something the popover class should deal with.

As of iOS7 contentSizeForViewInPopover is deprecated. You should use UIViewController's preferredContentSize property instead.
Hope this helps!

set contentSizeForViewInPopover in ViewDidLoad method in HomeViewController

contentSizeForViewInPopover is deprecated in iOS 7
The property: popoverContentSize of UIPopoverController represents the size of the content view that is managed by the view controller in the contentViewController property of UIPopoverController.
Reference
The advantage is that it is available in iOS 3.2 and later, so you don't need to check device version everytime, just replace contentSizeForViewInPopover method with your UIPopOverController object instance.

Have you tried:
setPopoverContentSize:<#(CGSize)#> animated:<#(BOOL)#>
in your segue logic block. Useful if you want the popover size to be variable based upon a data set, or view placement or whatever.

// self.contentSizeForViewInPopover = CGSizeMake(320.0, 600.0); //Deprecated in ios7
self.preferredContentSize = CGSizeMake(320.0, 600.0); //used instead

A little known trick to sizing your UIPopoverViewController to match the height of your UITableView is to use the tableView's rectForSection method to give you the height. Use the height in your viewController's contentSizeForViewInPopover like this:
- (CGSize)contentSizeForViewInPopover {
// Currently no way to obtain the width dynamically before viewWillAppear.
CGFloat width = 200.0;
CGRect rect = [self.tableView rectForSection:[self.tableView numberOfSections] - 1];
CGFloat height = CGRectGetMaxY(rect);
return (CGSize){width, height};
}

Try the following code:
- (IBAction)setData:(id)sender
{
UIViewController *popoverContent=[[UIViewController alloc] init];
UITableView *tableView=[[UITableView alloc] initWithFrame:CGRectMake(265, 680, 0, 0) style:UITableViewStylePlain];
UIView *popoverView=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 300)];
popoverView.backgroundColor=[UIColor whiteColor];
popoverContent.view=popoverView;
popoverContent.contentSizeForViewInPopover=CGSizeMake(200, 420);//setting the size
popoverContent.view=tableView;
tableView.delegate=self;
tableView.dataSource=self;
self.popoverController=[[UIPopoverController alloc] initWithContentViewController:popoverContent];
[self.popoverController presentPopoverFromRect:CGRectMake(400, 675, 0, 0) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
}

It looks like you want to do it programmatically, but in the storyboard, before you hook up a view controller to a segue, under the attributes inspector there is a "popover, use explicit size option." Maybe you can set the size that would work the best for your App first and not worry about the size with using code. Hope this helps, let us know how it goes.

Related

UIView Subclass backed by xib with Size Classes wrong frame

I am working on a project for iOS 7.0+ with a storyboard, using Size Classes with AutoLayout and I'm using a UIView subclass backed by a xib file of the same name.
What I'am trying to do is I'am instantiating a UIView from xib programmatically and adding it to a ViewController from a Storyboard. This ViewController has AutoLayout up and running but the UIView I am adding doesn't respect the frame of the ViewController.
I'm instantiating my UIView subclass like this:
tabBarView = [[SHDTabBarView alloc] initWithFrame:CGRectMake(0, self.view.height-50, self.view.width, 50)];
[self.view addSubview:tabBarView];
And inside the subclass I'm using a set up of creating a UIView IBOutlet called container to instantiate it form code like this:
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self == nil) return nil;
[self initalizeSubviews];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if (self == nil) return nil;
[self initalizeSubviews];
return self;
}
-(void)initalizeSubviews{
NSString *nibName = NSStringFromClass([self class]);
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
[nib instantiateWithOwner:self options:nil];
//Add the view loaded from the nib into self.
[self addSubview:self.container];
}
This is how my xib looks in the Interface Builder (notice the width of the canvas is 320 px):
And that's how it looks on the iPhone 6 (notice how it's getting cut off from the right side):
I've tried to use a multitude of solutions, including doing it all in code with an open-source solution PureLayout, using a manual constraint set up, etc.
None of my findings seem to work right. Ideally, I want to set up everything in Interface Builder, then just add the view to the superview of the ViewController with according frame and let AutoLayout do its magic.
How should I approach this task? Any advices are more than welcome.
Try to set the frame of your subview in the viewDidLayoutSubviews(). Looks like you init your subview before view fully layouted

Multiple Views in Xcode 4.2

I'm having a lot of trouble finding a tutorial for implementing multiple views in Xcode 4.2 without storyboard, this is for a class so I can't use storyboard yet. I'm just trying to have a 2nd view with a UIPicker come up when a button is clicked in the main view, I just can't find one for this version of Xcode and it's different enough from the older versions to confuse me.
Any help appreciated if someone can give me a quick description of what I need to do this or a newer tutorial I'd appreciate it :)
I think you should read the UIView Programming Guide to get a good handle on how UIViews work exactly. I find nibs/storyboard are really great at confusing new iOS developers.
In essence, a UIViewController has 1 view which you set in the viewDidLoad or loadView method by using the [self setView:someUIView]. You add more stuff to the screen by adding UIViews as a subview of the viewcontroller's "Main" view. For example
-(void)loadView {
// Create a view for you view controller
UIView *mainView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self setView:mainView];
// Now we have a view taking up the whole screen and we can add stuff to it
// Let's try a button, a UIButton is a subview of UIView
UIButton *newButton = [[UIButton buttonWithType:UIButtonTypeRoundedRect];
// We need to set a frame for any view we add so that we specify where it should
// be located and how big it should be!
[newButton setFrame:CGRectMake(x,y,width,height)];
// Now let's add it to our view controller's view
[self.view addSubview:newButton];
// You can do the same with any UIView subclasses you make!
MyView *myView = [[MyView alloc] init];
[myView setFrame:CGRectMake(x,y,width,height)];
[self.view addSubview:myView];
}
Now here we have our viewController who'se view is just a plain UIView which in turn has 2 subviews; newButton and myView. Since we created the MyView class, maybe it contains subviews as well! Let's take a look at what a UIView subclass could look like:
// Here is the init method for our UIView subclass
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Let's add a button to our view
UIButton *newButton2 = [[UIButton buttonWithType:UIButtonTypeRoundedRect];
// Of course, the frame is in reference to this view
[newButton2 setFrame:CGRectMake(x,y,width,height)];
// We add just using self NOT self.view because here, we are the view!
[self addSubview:newButton2];
}
return self;
}
So in this example we would have a view controller who'se view now contains 2 button! But the view structure is a tree:
mainView
/ \
newButton myView
\
newButton2
Let me know if you have any other questions!
Matt

adding subviews to UITableViewCell's content view makes cell unselectable

I want to add some static text to a UITableViewCell in a UITextView.
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setBackgroundColor:[UIColor clearColor]];
[addressField setFont:[UIFont fontWithName:#"HelveticaNeue" size:14]];
[addressField setContentInset:UIEdgeInsetsMake(0, 20, 0, 0)];
[addressField setEditable:NO];
[addressField setScrollEnabled:NO];
// change me later
[addressField setText:#"John Doe\n555 Some Street\nSan Francisco, CA, 00000"];
[cell.contentView addSubview:addressField];
[addressField release];
This works great but I this code makes the cell unselectable probably because the UITextView is covering the entire cell.
How can I work around this so that I can have both the UITextView and selectable cells?
btw, I could make the UITextView size a bit smaller but users would still not be able to select the cell if they touch the UITextView.
I think a slightly better way to do it is to create a tap gesture recognizer on the entire table. (For example in your viewDidLoad)
// gesture recognizer to make the entire cell a touch target
UITapGestureRecognizer* tap = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(changeFocus:)];
[tableView addGestureRecognizer:tap];
[tap release];
Then you create a selector (changeFocus: in this case) to do the actual selecting.
- (void)changeFocus:(UITapGestureRecognizer *)tap
{
if (tap.state == UIGestureRecognizerStateEnded)
{
CGPoint tapLocation = [tap locationInView:self.tableView];
NSIndexPath* path = [self.tableView indexPathForRowAtPoint:tapLocation];
[self tableView:self.tableView didSelectRowAtIndexPath:path];
}
}
You can make your changeFocus method more elaborate to prevent selections or give focus to specific subviews of the selected indexPath.
I would adopt the following approach in order to keep interaction enabled with both the UITextView and the UITableViewCell.
Declare your controller class (a UITableViewController I guess ?) as UITexView delegate.
When you declare your UITextView, set the table view controller as it's delegate.
Implement one of the UITextViewDelegate methods (ex : - (void)textViewDidChangeSelection:(UITextView *)textView) in your table view controller .m file.
From within this method you can manipulate the targeted cell either with a custom code or by triggering the tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *) delegate method through selectRowAtIndexPath:animated:scrollPosition:.
Your code might then look like :
In the table view controller .h file :
#interface MyTableViewController : UITableViewController <UITextViewDelegate> { ...
...
}
In the table view controller .m file :
UITextView *addressField = [[UITextView alloc] initWithFrame:CGRectMake(0, 0, 300, 75)];
[addressField setDelegate:self];
...
Then implement this function for example (or any other suitable UITextViewDelegate function) :
- (void)textViewDidChangeSelection:(UITextView *)textView {
// Determine which text view triggered this method in order to target the right cell
...
// You should have obtained an indexPath here
...
// Call the following function to trigger the row selection table view delegate method
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]
}
Note that there are other alternatives like subclassing UITextView and deal with it's touch methods. I would recommend to use the possibilites offered by its delegate protocol though.
Note also that it might be handy to have your UITextView declared or at least referenced as an instance variable of the table view controller class. This will help you easily keep track of which addressField was hit and get the right indexPath.
[addressField setUserInteractionEnabled:NO];
I hope this helps you a bit:
[self.view insertSubview:TextView aboveSubview:TableView];
Or vice-versa based on your requirements.

UINavigationBar Background Image Problem

i have set my navigationbar background with this code in my App delegate:
#implementation UINavigationBar (UINavigationBarCategory)
- (void)drawRect:(CGRect)rect {
UIImage *backgroundImage = [UIImage imageNamed: #"Nav-rightpane.png"];
CGContextDrawImage(UIGraphicsGetCurrentContext(),
CGRectMake(0, 0, self.frame.size.width, self.frame.size.height),
backgroundImage.CGImage);
}
#end
This works perfect but now i must change the background to the default at UIPopoverController. Any idea for this?
Thanks
Do not use this solution. Simply delete your UINavigationBar Category and implement your own UINavigationController.
#interface XNavigationController : UINavigationController{}
and change override viewDidLoad method in implementation
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *img = [UIImage imageNamed: #"Nav-rightpane.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:img];
[imageView setFrame:self.navigationBar.bounds];
[imageView setContentMode:UIViewContentModeScaleToFill];
[self.navigationBar addSubview:imageView];
[imageView release];
}
so use this navigationController when you want to change the background of navigation bar.
The correct solution pre-iOS5, as recommended by Apple engineers is to not use categories and to not use method swizzling. This is confirmed to break on iOS 5 which is very close to being released.
Instead, you should create a subclass of UINavigationBar, override the drawRect: method, and then use Interface Builder to define your navigation controller. Interface Builder will let you change the classname for the navigation bar to your custom subclass:
If you want to avoid Interface Builder, you can create a temporary XIB and load an instance of the navigation controller from it, then use NSKeyedArchiver to archive it to a file. Then use xxd -i <filename> on the command line to generate C-code for the raw bytes of the navigation controller. Then paste that into a source file and unarchive it when you want an instance.
It seems like a lot of work, but the WWDC videos and engineers on the forums have repeatedly stated that this is the only safe and reliable way to do what you want.
In iOS 5 this all becomes a whole lot easier.

drawRect not being called on added subview

I'm trying to programmatically create a window with custom contentView and one custom NSTextField control, but i'm having trouble getting this hierarchy of window and views to draw themselves.
I create a custom borderless window and override it's setContentView / contentView accessors. This seems to work fine and custom contentView's initWithFrame and drawRect methods get called causing contentView to draw itself correctly.
But as soon as i try programmatically adding custom NSTextField to contentView it doesn't get added or drawn. By saying custom i mean that i override it's designated initializer (initWithFrame:frame - only for custom font setting) and drawRect method which looks like this:
- (void)drawRect:(NSRect)rect {
NSRect bounds = [self bounds];
[super drawRect:bounds];
}
The custom contentView's initializer looks like this:
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self != nil) {
// i want to draw itself to the same
// size as contentView thus i'm using same frame
CustomTextField *textField = [[CustomTextField alloc] initWithFrame:frame];
[self addSubview:textField];
[self setNeedsDisplay:YES];
}
return self;
}
I've been strugling with this for few hours so any pointers are much appreciated. More code is available on request .)
Your -drawRect: override seems wrong to me, why would you deliberately ignore the passed in rect argument? Why is this necessary?
As for why the text field is not appearing, it's most likely because you have not configured it. When you create an NSTextField in code, you don't get the same thing as the default instance that you get when you drag a text field onto a view in IB. You will need to configure the NSTextField and its NSTextFieldCell to get the appearance you desire.
I am using a programmatically added text field that I configure like this:
_textField = [[NSTextField alloc] initWithFrame:textFieldRect];
[[_textField cell] setControlSize:NSSmallControlSize];
[_textField setFont:[NSFont systemFontOfSize:[NSFont smallSystemFontSize]]];
[_textField setBezelStyle:NSTextFieldSquareBezel];
[_textField setDrawsBackground:YES];
[_textField setBordered:YES];
[_textField setImportsGraphics:NO];
[_textField setAllowsEditingTextAttributes:NO];
[_textField setBezeled:YES];
[_textField sizeToFit];
[self addSubview:_textField];
[_textField setFrame:textFieldRect];
[_textField setAutoresizingMask:NSViewMinXMargin];
Thanks for your answers!
I found my own problem. The reason why drawRect wasn't being called is because custom textfield was drawn outside the frame of content view. I guess i forgot to mention crucial detail that i'm drawing window centered on the screen thus it's frame was with x/y offset.
To fill the window with it's content view I init contentView with same frame as window (meaning the same (x;y) offset from window's (0;0) point).
Now i'm just unable to write to custom text field, but that's another problem I think I'm able to handle.