As of OSX 10.7 if you wish to perform a manual layout of a NSView you should do so by overriding the layout method and whenever you wish to schedule a call to this method you simply do:
[myView setNeedsLayout:YES]
I am very familiar with this pattern in iOS however on OSX it does not seem to work. I have created a custom NSView and implemented layout but it seems that it never gets called. Ever. Not after adding subviews, not after calling setNeedsLayout:YES, not ever and I don't know why. I can manually call layout and things work as expected but the docs say never to do this.
From Xcode:
- (void)layout
Override this method if your custom view needs to perform custom
layout not expressible using the constraint-based layout system. In
this case you are responsible for calling setNeedsLayout: when
something that impacts your custom layout changes.
From the online docs:
You should only override layout only if you want to do custom layout.
If you do, then you also need to invoke setNeedsLayout: when something
that feeds into your custom layout changes.
Link: http://developer.apple.com/library/mac/#releasenotes/UserExperience/RNAutomaticLayout/#//apple_ref/doc/uid/TP40010631-CH1-SW14
Any help is much appreciated.
UPDATE:
Here is a link to a sample Xcode project that illustrates the problem LayoutTest.zip
You need to enable autolayout. If you're using a xib file (and you should be, no commando!) you can check the autolayout checkbox in the Interface Builder Document part of the file inspector.
In code:
[myView setTranslatesAutoresizingMaskIntoConstraints:YES]
In your sample project, you have no constraints. The view won't call layout without any constraints.
NSView *aView = self.window.contentView;
NSDictionary *viewsDictionary=NSDictionaryOfVariableBindings(aView, complexView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[aView]-[complexView]" options:0 metrics:nil views:viewsDictionary];
for (NSLayoutConstraint *constraint in constraints) {
[complexView addConstraint:constraint];
}
Related
I have a header that I would like to add to every view in my program. Rather than manually do this I've been trying to find a way to insert this "header view" above every view that gets loaded. In the sample below, I tried using the insertSubView method but this inserts the entire view and not just the tiny bit of header content I'm interested in.
[self.view insertSubview:self.headerController.view aboveSubview:self.indexController.view];
[self.view insertSubview:self.indexController.view atIndex:0];
Does anyone know how to do this correctly? I'm using Xcode 5.
You're inserting the views of the headerController and indexController on your other view controllers' views. Generally, though, you want to be careful about keeping your view hierarchy synchronized with your view controller hierarchy. The importance of this is discussed in some detail in WWDC 2011 video Implementing UIViewController Containment.
So, generally you'd instantiate the appropriate indexController and headerController, call addChildViewController, add the subviews, and then call didMoveToParentViewController for each of those two view controllers. For more information, see Creating Custom Container View Controllers section of the View Controller Programming Guide for iOS. But you app's view controllers might do something like:
HeaderController *headerController = ... // instantiate however is appropriate
[self addChildViewController:headerController];
headerController.view.frame = [self frameForHeaderController]; // define the frame/constraints as appropriate
[self.view addSubview:headerController];
[headerController didMoveToParentViewController:self];
You'd then repeat that process for the indexController.
Just make sure to (in addition to doing the custom container calls) either set the frame of the view controller's frame or define auto-layout constraints that will dictate the placement of the view (like you do for any programmatically added view). If you don't specify the frame, it may well end up being CGRectZero, which is obviously not what you intended.
You might, though, consider flipping this around. Make a custom container view controller that includes your header and any other UI elements that persist for every view. Then make your app's view controllers as child controllers of that container "parent" view controller. If either the header controller's view or the index controller's view navigates your app between various content view controllers, this sort of structure might make more sense.
I think if you watch that WWDC video and/or review the Creating Custom Container View Controllers, this will make more sense.
I'd suggest to create a base view from which you will subclass for every views that need this header.
You could have a BaseView.h that would look like:
#interface BaseView : UIView
#property (nonatomic) UIView *someHeaderView;
#end
And the implementation (.m) looking like this:
#implementation BaseView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.headerView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, CGRectGetWidth(frame), 150.0)];
[self.view addSubview:self.headerView];
}
return self;
}
#end
Then have every views that requires a header subclass it:
#interface SomeViewRequiringHeader : BaseView
#end
Try reversing the order of the two:
[self.view insertSubview:self.indexController.view atIndex:0];
[self.view insertSubview:self.headerController.view aboveSubview:self.indexController.view];
Also, since headerController is a UIViewController, it probably has a nib file with a default size of full screen. If you want to only be a certain height, you have to modify that nib file with the appropriate height.
I need to create a cameraoverlay view(to add on ZBar sdk reader), my question is how can I create all objects I need not programmatically. right now I create all objects programmatically and then add them to myView, and use myView as cameraoverlay view.
[self.myView addSubview: myImage];
[self.myView addSubview: self.mylabel];
[self.myView addSubview: myButton];
reader.cameraOverlayView=self.myView;
I tried to add another control view and added a view to it then made a tabbar and tried this code which doesn't work:
TestViewController *test=[[TestViewController alloc]init];
reader.cameraOverlayView=test.testView;
making objects programmatically is hard for me is this possible to find source code for objects which created in xcode, for example when I create a custom button in xcode can I find source code which has generated for this button and just copy it in my program.
There are several approaches that will work
Create a custom conainer view in code. Add the zbar view, and any custom views (scanner graphics, etc on top). This is what i recently did.
Create a xib-baased view, and include the zbar view in that. See the 'object' component.
Load custom views from xibs and add them.
If you have the time, I recommend you invest to learn how to create views programmatically.
This involves:
in your view contoller override the loadView method.
return a custom sub-class of UIView that contains sub-views. These include the zbar view and your overlays.
learn how to use the layoutSubviews method on UIView. Hint set your child view's frame relative to thecparent views bounds.
There are loads of custom components on Github. Check out DCSwitch or CMPoptip, or read up on contributions to ManiacDev.
* As requested - adding subviews after successful scan *
- (void)readerView:(ZBarReaderView*)view didReadSymbols:(ZBarSymbolSet*)symbols fromImage:(UIImage*)image
{
for (ZBarSymbol* symbol in symbols)
{
[self presentScannedOverlay];
[_scanInProgressOverlay setAnimating:NO];
[_readerView stop];
[_delegate didScanPayload:symbol.data];
break;
}
}
- (void)presentScannedOverlay
{
//Be sure to override layoutSubviews, so that you can position the view below,
//relative to its parent. . if you already know the size of the parent, just replace
//CGRectZero with CGRectMake(some values)
_scannedOverlayView = [MyOverlayView alloc] initWithFrame:CGRectZero];
[self addSubView:scannedOverlayView];
}
iOS 5 adds a nice feature allowing you to nest UIViewControllers. Using this pattern it was easy for me to create a custom alert view -- I created a semi-transparent view to darken the screen and a custom view with some widgets in it that I could interact with. I added the VC as a child of the VC in which I wanted it to display, then added its views as subviews and did a little animation to bring it on the screen.
Unfortunately, I need to support iOS 4.3. Can something like this be done, or do I have to manage my "alert" directly from the VC in which I want to display it?
MORE INFO
So if I create a custom view in a nib whose file owner is "TapView" and who has a child view that is a UIButton. I tie the UIButton action to a IBAction in TapView.
Now in my MainControllerView I simple add the TapView:
TapView *tapView = [[TapView alloc] init];
[[self view] addSubview:tapView];
I see my TapView, but I can't interact with the UIButton on it and can interact with a UIButton on the MainControllerView hidden behind it. For some reason I am not figuring out what I'm missing...
Not sure if this helps, but, in situations where I've needed more control over potential several controllers, I've implemented a pattern where I have a "master" controller object (doesn't need to be descendent from UIViewController), which implements a delegate protocol (declared separately in it's own file), and then have whatever other controllers I need to hook into declare an object of that type as a delegate, and the master can do whatever it needs to do in response to messages from the controllers with the delegate, at whatever point you need; in your case, that being displaying the alert and acting as it's delegate to handle the button selection. I find this approach to be very effective, simpler and usually cleaner. YMMV ;-)
Regd. your second query, where you are trying to create a custom view using nib. Don't change the FileOwner type, instead set "TapView" for the class property of the top level view object.
Once you have done this, you might experience difficulty when making connections. For that just manually choose the TapView file for making connections.
Also, to load the view you need to load its nib file. For which you can create a class level helper method in TapView class like below
+(TapView *) getInstance
{
NSArray *bundle = [[NSBundle mainBundle] loadNibNamed:#"TapView" owner:self options:nil];
TapView *view;
for (id object in bundle) {
if ([object isKindOfClass:[TapView class]]) {
view = (TapView *) object;
break;
}
}
return view;
}
Now, you get a refrence to you view object like this
TapView *tapView = [TapView getInstance];
I have a app out for testing right now that's almost completely done - just a few bug fixes left. Unfortunately, the customer decided that they'd like the entire main page of the app to be inside of a scroll view (so that there's more room for the table at the bottom). I already have everything set up and I don't really want to move everything and change references. Is there an easy way to change the class of the main view to a scroll view? I've already tried changing the class in IB and setting the class type in the init method. If there isn't I'll probably just throw the top section of the view into a nib file and load it as a custom cell.
-EDIT- I ended up changing the class type in IB and then doing
[(UIScrollView *) self.view setScrollEnabled:YES];
[(UIScrollView *) self.view setContentSize:CGSizeMake(0,2000)];
in viewDidLoad. Thanks for the help, wish I could accept all your answers.
When you are referring to [self view], I am going to assume you mean in a view controller. The view of a view controller can be any view that derives from UIView. Thus a scrollview is completely acceptable.
I don't really want to move everything and change references.
what would you have to move? why would you have to change references? Only thing you should need to do is add a scroll view to your view controller, set the view controllers view to it, and add the current view as a subview to the new scroll view. No references need to be changed, nothing has to be moved.
Refer to loadView method in documentation of view controller.
Here is a simple (untested!) example
- (void)loadView {
UIScrollView *scrollView = [[UIScrollView alloc] init] autorelease];
//Set the properties of scrollview appropriately....
self.view = scrollView;
}
Now the root view of your view controller will be a scroll view.
Note
- As the documentation states, do not do this if you are using interface builder to initialize your views/view controller. I could not tell from your description if this was the case or not. If it is, you should be able to change the view type in interface builder.
You need to set the contentSize property of your scrollview.
Since you are using IB, the easiest way to do this is to put all your UI elements into a view and add this single view to your scroll view. In the viewDidLoad method, set the content size of the scrollview to be the same size as the view that contains all your UI.
As an aside, there are much easier ways to reference views than walking down the view hierarchy, as you seem to be doing. viewcontroller.view.something.tableview. Add a connection to the tableview from your view controller in IB and it doesn't matter where that tableview is in the view hierarchy. You'll always be able to reach it from viewcontroller.tableview, no matter how you rearrange your nibs.
I think you have to use a pointer with proper type. Example for Google Maps: let's say you changed you base view's class to GMSMapView.
MapViewController.h
#property GMSMapView *mapView;
MapViewController.m
-(void)awakeFromNib{
[super awakeFromNib];
self.mapView = (GMSMapView*)self.view;
// ... etc.
}
This should be straight forward for a guru. I don't have any code really written out, just a couple of controllers and a custom UIView. All connected through nibs. The app loads without crashing, yet I can't see my NSLog() hit from my custom UIView.
My application delegate has default template code which calls for a class of mine called TabAnimationController. TabAnimationViewController has its view set to TabView. I made sure that in TabAnimationViewController's NIB that File's owner is set to TabAnimationViewController and that my instance of UIView has its class set to TabView.
In TabView.m I'm trying to see how NSLog is going to hit, and it's not showing up at all.
- (void)loadView {
NSLog(#"calling loadView");
}
- (id)initWithFrame:(CGRect)frame {
NSLog(#"Calling initWithFrame:");
return self;
}
Strange. I'm not sure why even after proper IB connections that my NSLog will not show up. Only anything put into drawRect: will invoke. Why isn't initWithFrame or loadView ever get hit? What if I want to customize this view programmatically?
First of all, when a view is dehydrated from nib file, instead of initWithFrame, initWithCoder is invoked. So you need to implement your initialization in initWithCoder as well. (It may be a good idea to keep the initWithFrame initialization as well, if you anticipate programmatically creating your TabView instead of hooking up in the IB. Just refactor your initialization to another method and call it from both implementations.)
Also in your initialization code above you must always call the super class's initialization. There is a boiler plate pattern all custom classes use in their init implementation for that:
if (self = [super initXXX]) { do your initialization }
return self;
Second, loadView which is actually a UIViewController method and not a UIView method is invoked only if the view outlet of the controller is nil.
Unless you are composing your view yourself programmatically using your controller, you do not need to override loadView. Instead you should override viewDidLoad, which is called after the view is loaded, to do additional initialization.
The simplest way to get this up and running is simply to use the "View based Application" template when you create a new project. It sets up everything you need to start with.
But, in short, you're looking at the wrong methods. First, you shouldn't override loadView unless you're creating your view programatically. If it's loading from a XIB file look at the initWithNibName method.
You might also want to look at the viewDidLoad, viewWillAppear and viewDidAppear methods that are triggered, well, it's fairly obvious when!