MVC: Where to load views? - iphone

I have a relatively simple app. I have a fullscreen UIView subclass (musicGridView) that the user interacts with, and a UIScrollView subclass (scrollView) that loads this class in, as well as a ViewController and mainWindow.xib.
Right now, I have a viewController instance loaded in the nib, which is of type myViewController. The nib also has instance of myUIView, which the myViewController is pointed to.
In AppDelegate, I do this:
[window addSubview:scrollView];
[scrollView addSubview: viewController.view];
scrollView.musicGridView = (MusicGridView*) viewController.view;
which I think is wrong. Ideally, the appDelegate doesn't have an instance of scrollView.
In scrollView's AwakeFromNib class, I do the initialization on the scrollView.
The way I think it should go, is load the [window addSubview:viewController.view] in appDelegate, and then point the viewController to an instance of scrollView instead of musicGridView. Then, in scrollView awakeFromNib add a subview of musicGridView.
Eventually, I want to create another view owned by scrollView that is actually a side TabBar (which isn't possible with the given API's) that the user can scroll to the left to reach.
So I guess amongst other MVC stuff, the question is, should the viewController point to the scrollView, which contains all other content UIView subclasses, including musicGridView?

It sounds like you are not using Interface Builder to design your UI. Since this is a new project, I would suggest doing that. You should not have to write any code like this at all. You will however need to describe your outlets and actions.
The two most important things you need to know when dealing with IB are the IBOutlet and IBAction keywords.
Sample class header:
#interface MyClass : NSObject {
IBOutlet UIScrollView* myScrollView;
}
- (IBAction) doWork;
#end
Now you can wire these two things up using Interface Builder by dragging sources to destinations with your mouse. YouTube has lots of tutorial videos on IB if you need a better description of how to do this.

I'm someone who doesn't use Interface Builder for UI design; in my experience, you need to go either all-IB, or all-programmatic. To solve your particular problem, I think you need your musicGridView to be an instance or extension of UIScrollView.
This can be done in your custom viewController's loadView method - simply initialize it to a UIScrollView (and add things to it), instead of a plain old UIView.
However, as described above, this approach isn't compatible with an IB-centric approach, as is confirmed in the UIViewController Class Reference
All subclasses of UIViewController
should implement the loadView method
to set the view property unless you
load your view from a nib file.

Related

Interface Builder won't allow connections to custom UIView class?

Using Xcode 4.3.3, I can't figure out how to connect outlets in a custom UIView class with objects created in Interface Builder.
In one ViewController, I have a variety of buttons, sliders, etc that I'm trying to group into Views. So, within that ViewController in IB, I added 3 views. Only 1 view will be visible at any given time.
I derived custom UIView classes to handle each of these 3 views. My view controller instantiates each of the classes. I selected the view(s) in IB, opened the Identity Inspector and set the Class(es) to my custom class(es). But when I tried dragging connections from the view and/or it's constituent controls to the custom view's .h file IB won't add a connection.
IB allows me to add outlets/actions by dragging to the parent view controller's .h, but not to the custom view's .h file. I thought once I set the View's class to be my custom class, I could drag outlets for the view's components to my custom class rather than in the view controller.
This question seems identical to mine: how to connect UIview outlets to a custom subview The two solutions (manually adding outlets and setting the view's class in IB) did not change the behavior for me. Here are the manual outlets I added:
customView3.h
#import <UIKit/UIKit.h>
#interface customView3 : UIView
#property (retain) IBOutlet customView3 *view3;
#property (retain) IBOutlet UISlider *slider;
#end
customView3.m
#import "customView3.h"
#implementation customView3
#synthesize view3, slider;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
#end
What am I missing here? What else do I need to set/check in order to add outlets to my custom UIView rather than the view controller?
Note that I did get this to work today, but had to insert/type the outlets by hand in the derived class, then I could drag from the header file to the UI element in the Storyboard, but not the other way around.
I opened a bug with Apple. If anything interesting happens, I'll update this answer.
Updated 10/4/13: After much research, it seems the root of the problem is that IB believes that I'm trying to connect objects to properties within a class, that I'm going to then create a swarm of instances of this class and then IB won't be able to tell which instance should handle the messages. So, it disallows the connection on the belief that I'm (potentially) making simultaneous connections from one object to multiple copies of a property across several instances of the class.
My view is that I only want a single instance of the custom UIView, and that I should be able to tell IB, "don't panic, you're only dealing with this one instance."
I'm now focused on a new project and haven't had a chance to retry this in Xcode 5; however, given the info above, I'm not optimistic it will work in Xcode 5, either.
What you're trying to do is certainly both sensible (IMO) and possible. XCode is however quite quirky when it comes to establishing connections to outlets in subviews.
I've found that cleaning, rebuilding and sometimes restarting XCode tends to help, but sometimes it just doesn't work. In some cases I got it working by manually creating the outlet and dragging from the outlet to the control rather than the other way.
This is not very helpful, I know, but I just wanted to make it clear that this is supposed to work and when it doesn't it is most certainly due to a bug in XCode.
In my experience, you add IBAction and IBOutlet to subclasses of UIViewController and not subclasses of UIView. So within the MVC model, your "1" UIViewController has all of these IBOutlets in it and then you use the data from these IBOutlets to update your views.
So you are fine creating the UIViews objects in IB and then creating the corresponding classes in code and then changing the class of the UIViews objects in IB to your UIView subclass. Just put all IBOutlets in the UIViewController that has all the UIViews under it, get the values you need from the IBOutlets and then pass them down to (set them in) your UIView subclasses.
Hope this helps.
I had the same problem and found a workaround:
1) Open the Assistant Editor with customView3.xib on the left and customView3.h on the right
2) ctrl + drag from UISlider in customView3.xib to the customView3.h code on the right
3) An option to create a new Outlet connection will pop up.
4) Name the connection and click connect
It will then create a connected IBOutlet.

Will touchesBegan method called without UIView or UIViewController in iPhone?

I created an app in where i have appdelegate class only. I dont create a UIView or a view controller. I just added textFields, labels and buttons in my window.
I want to hide the keyboard when the user taps out of the text boxes. This is normally achieved by the method touchesBegan. For implementing this method, two things we need to do.
One is add in the header file(.h)
Another one is set the textField's delegate. Thats is, textField.delegate=self;
I did those two things. But still the touchesBegan method did not called.
Why? Should i have a UIView or UIViewController class for it?
Thanks in Advance
UIWindow is a subclass of UIView, so you could just hook into those. But this is a seriously bad design pattern. Consider modifying your application so it uses a single view controller, rather than doing everything in the app delegate. Otherwise you're jury-rigging the app delegate to be something it was never designed to do.
touchesBegan is a UIView method. if you want to do it in your setup you should create a UIView subclass, put all your controls in that one and implement touchesBegan in the UIView subclass.

iPhone: viewWillAppear is not called for UIView

I have created an UIView in my iPhone app. I want to handle something when user closes or opens when UIView is present as current screen. I thought, i can do this under viewWillAppear:. But, viewWillAppear: is not called in UIView. Does it work only on UIViewController? How can i handle viewWillAppear: or viewDidAppear: for an UIView?
Update: UIView what I created everything through program, not in .xib.
Please advise.
Thanks!
From your message I infer that you wrote your viewWillAppear: method on the UIView class. As you suspect, that method is part of [UIViewController]1, not [UIView]2 therefore it only gets called on the UIViewController.
You should connect the property view of the UIViewController to the UIView object in the interface builder and then implement that method in the UIViewController.
If your view is created in response to an user action,
Update for your update:
You should tag the views either in code (view.tag=1) or IB.
Then you can do if (self.window.rootViewController.view.tag == 1) { ... } from your delegate (assuming you are looking for the view of the controller who is the rootController, otherwise post more details).
It's even better if you define constants on one place instead writing 1 as a literal.
These delegate methods are called every time the superview is presented to the screen and should be implemented in the UIViewControllers.
The gotcha is that these methods aren't called when subviews are presented on the screen, so your superview-view-controller will have to respond to these events accordingly.
You can find more information in this post here.
If you study the documentation for UIView and UIViewController what you will find is -(void)viewWillAppear:animated: is a method of UIViewController and not of UIView, so in order to use it, it must be implemented by subclassing UIViewController. Generally for best practice if you want to follow MVC, any functionality that does not pertain to the view itself should be delegated to the view controller and not be in the body of your UIView subclass.
Create a new view controller with xib file, and then link your custom view class to the view in your xib file.

UIView build in Interface Builder connected to my custom UIViewController

I was trying to use IB in a slightly different way that I am use to and I can't get it working extending the normal approach I use, when dealing with IB.
Instead of making a new UIViewController and have the view XIB generated for me and everything linked together by Xcode, I would like to just build a small (320x40px) View XIB and link it to my already existing ViewController.
I start out by making a new file in Xcode, select "view XIB".
I then open IB and add some labels etc. to the view and I set "Files Owner" to be my existing ViewController.
In my existing ViewController I set the IBOutlets for the labels etc. I put in my view.
I go back to IB and hook up the UILabels to my outlets in "Files Owner".
I would now think that I have a reference to the labels inside the XIB, in my viewController.
This is not really the approach I would like, I see no need for my viewController to have a reference to labels inside my view.
How I usually do in code:
My ViewController controls a bunch of UIViews made entirely in code and who instantiate them by:
UIView *customView = [[CustomView alloc] initWithFrame:aFrame];
[customView setTag:MY_CUSTOM_VIEW];
[customView setDelegate:self];
[self.view addSubView:customView];
[customView release];
After this I would access the labels, buttons etc. from my controller by using the [(UILabel*)[[self.view viewWithTag:MY_CUSTOM_VIEW] myLabel] setText#"Hello, World"];
have my UIViewController implement what ever methods the customView protocol required.
How to get that functionality with IB
Should I first build a MyCustomView class extending the UIView class, have it hold all my IBOutlets, set MyCustomClass as files owner and then instantiate that as shown above?
Is it OK to have a view act as viewController for the IB view and how would I relay actions to my "real" viewController?
What I would like to achieve is to deal with instantiating and laying out several UIViews in my UIViewControllers code, but have the freedom of designing some of these UIViews in IB.
All the info I can find is regarding the standard "build a UIViewController with a XIB for the view" or "How to build libraries of IB components".
I hope it makes sense and thanks for any help given:)
You can create whatever view structure you want in Interface Builder and then instantiate it using the UINib class. Once you create an UINib object it loads the contents from the nib and keeps them. Then, whenever you send it the instantiateWithOwner:options: message, it will instantiate the objects contained in the xib and return an array with the top level views. You can then add these views to your view hierarchy and handle them just like any other view you created programmatically.
If you keep the UINib object (as a property for example), you can instantiate the contents again and again, which allows your xib to be used like a template.
update: For a pre-iOS 4 workaround see my recent question and answer.

How can I use an UIScrollView in an Multiview-Application?

I have:
- an AppDelegate class
- two viewController classes
- three nib files (MainWindow.xib, firstView.xib, secondView.xib)
I want:
The secondView.xib should have an UIScrollView inside, so that I can add a lot of stuff to the view and it can be scrolled down.
Where would I have to conform to the UIScrollViewDelegate protocol now? I feel the AppDelegate class is the wrong place, because its far away from that secondView and not all my views want to use a UIScrollView.
since UIScrollView is located in the secondView.xib I assume its functionality is limited to that view. If you have an instance of UIViewController that serves as the controller for the secondView.xib then the best place to implement UIScrollViewDelegate would be on that UIViewController directly. You are correct that implementing it on the AppDelegate doesn't make sense if you have a more specific derived controller where it should belong. Hope this helps!