iminiB3D - cocoa touch UI on openGL

BlitzMax Forums/MiniB3D Module/iminiB3D - cocoa touch UI on openGL

Beaker(Posted 2009) [#1]
Anyone had any joy getting a Cocoa Touch user interface up and running with callbacks etc on top of iminiB3Ds GL view? I can add a text field (for example) over the view using Interface Builder no problem, but I can't quite work out how or where to add callback response code. Any pointers are much appreciated. I don't mind getting dirty with Objective C, but I'm not very familiar with Cocoa/Mac stuff.

(I realise this doesn't come highly recommended because of performance issues but I really need a simple interface at app startup).

Thanks in advance.


ima747(Posted 2009) [#2]
I haven't gotten that far with any of my iminib3d projects, but I have it working in cocos2d which uses a similar openGL window setup. I'll see if I can poke some code in the next day or so for you if no one gets back to you first.


JaviCervera(Posted 2009) [#3]
You should write a subclass of UIViewController. Now create an IBOutlet property on you AppDelegate class to store the controller.

On your interface Builder document, create and instance of your view controller and connect it to the outlet in the AppDelegate. Next, move your GLview & subviews from the Window object to the Controller object, and connect the "view" outlet of the controller to the GLView.

Since the GLView is not anymore inside the window, it will not show up with it at application launch, so go to the "applicationDidFinishLaunching" of your AppDelegate class, and add to the bottom of it:

[self.window addSubview:self.glViewController.view];

(assuming that "glViewController" is the name you gave to the property in the AppDelegate that holds the controller).

Now everything should work the same way it was doing before, with the addition that you have a controller in your application in which you can define methods to respond to your button events.

In order to implement such a method, you just have to set IBAction as the return type of your method, and make sure to declare it in the header for the controller. On your Interface Builder document, connect the "Touch Up Inside" action of the button to the methods you just have created (the list of methods available will show up in IBuilder when you link the action of the button to the controller object).

And that's all :)


ima747(Posted 2009) [#4]
Finally got a chance to take a look at this. It works as I suspected.

As far as I know the only way to handle an interface on iPhone is with objective C. It would be a good idea to do some digging into the standard SDK examples to get a feel for how it's layed out. The most important thing I've gotten my head around is working with view controllers rather than views.

Here's a run down on how to get a custom view controller (and by extension anything you can toss together in IB) to show in an iMinib3d app without modifying the iminib3d base xib.

Create new UIViewController subclass files in XCode (file->new file->cocoa touch->UIViewController Subclass), tell it to create the XIB for your as well. Name it (in this case it's name is "MyCustomeViewController"), poke through events, setup custom actions etc. (read up on interface stuff with objective C, I think it's needlessly complicated in layout but actually pretty smooth in execution once you get past it). This has all the code for the view, handles all the events you set, contains any variables etc. etc.

Back in your iminib3d code include AppDeligate.h wherever you would like to be able to add sub views from along with the header for a custom view controller you've made.

Then when you want to throw the view controller use the following.



your viewcontroller will probably want to remove itself at some point, so you'll want something like the following in it you can trigger with a close button, or wherever. This should free the view controller's memory along with dumping it. I'm no whiz with the memory system yet so don't be too surprised if someone corrects this.

-(IBAction)closeViewController:(id)sender {
	[self.view removeFromSuperview];
	[self release];
}


Hope that helps!


JaviCervera(Posted 2009) [#5]
You really don't need another Interface Builder document for the ViewController, my suggestion is to create the first one on the MainWindow.xib itself. Also, it's probably cleaner to do as much as possible in Interface Builder itself, instead of creating the view controller programatically.

There's some weird things done in ima747's example. Here you keep the GLView as a subview of the window, and then you add a subview which is controlled by a UIViewController. The problem is that the UIViewController is supposed to control the root view of your interface, and not any subviews of it. If you want to control a subview, if it descends from UIControl, you assign a responder method from it. Other UIView subclasses have the concept of a "delegate", which is an object which adopts a specific protocol and is notified of all the events occuring on the view. Also, the view asks the delegate about some information it needs. An UIViewController is just a special type of delegate. It is supposed to be the delegate of the root view.

While I can't see if the approach ima747 suggested will lead into real problems, the layout is not very clean.

If you just want to do it the simplest way, just add the method to respond to the button to your AppDelegate and connect it on Interface Builder. That way you don't have to create any ViewControllers at all.


ima747(Posted 2009) [#6]
If you look through the examples from the SDK new view controllers are always split into seperate XIB files. I personally would prefer to keep them all together but if you get a number of views it is in keeping with the the idea of everything in it's own file that apple seems fond of.

Additionally that method was the one that the cocos2d community seems to conform to, and since cocos runs very simmilarly to iminib3d, in that there's a master opengl view and that's it, it makes sense to me to continue with that method.

Also you are not necisarilly supposed to have only 1 view controller. You are supposed to have one for each view. Look into some examples using UINavigation controllers, they don't change views they change sub view controllers. Doesn't make any sense to me why they do it like this but that's how apple set it up.

However it does make sense to me to add the new view as a sub view of the opengl view for 3 reasons. From a layout perspective usually your inputs are going to be called from within your game. i.e. you get a high score and are asked to enter your name, you throw up a view with a name input field. and 2, you can completely remove the view controller from memory when you're not using it leaving more room for game stuff, so there's no overhead when you're not using it. and third you don't have to modify the core XIB for iminib3d so upgrading to new versions (should the XIB ever be changed) is a snap.

IMO there's no wrong way, just a way that makes more or less sense in a specific situation.


JaviCervera(Posted 2009) [#7]
Yes, the usual way to split ViewControllers is to put each on a separate document, but there is no problem in putting the first one on the mian view. Some templates do it this way.

It makes sense the way navigation and tabbar controllers works. These controllers handle the navigation through views, and delegate to the ViewController of the selected view all the other logic.

What I was saying about having the ViewController for the root view is the way it has to be done, and so says Apple (from the UIViewController class documentation):

You use each instance of UIViewController to manage a full-screen view. For a simple view controller, this entails managing the view hierarchy responsible for presenting your application content. A typical view hierarchy consists of a root view—a reference to which is available in the view property of this class—and one or more subviews presenting the actual content. In the case of navigation and tab bar controllers, the view controller manages not only the high-level view hierarchy (which provides the navigation controls) but also one or more additional view controllers that handle the presentation of the application content.


And also:

Note: You should not use view controllers to manage views that fill only a part of their window—that is, only part of the area defined by the application content rectangle. If you want to have an interface composed of several smaller views, embed them all in a single root view and manage that view with your view controller.



Beaker(Posted 2009) [#8]
Thanks for your replies. I'm going to try both ways and see which works best (or at all) for me over the next couple of days.

Jedive
Since the GLView is not anymore inside the window..
Do I still need the Window object or can I delete that?

ima747
tell it to create the XIB for your as well.
I don't see anywhere to do this when you create the viewController subclass. Am I missing something?

Thanks again guys.


ima747(Posted 2009) [#9]
I think it depends on what you're doing...


You use each instance of UIViewController to manage a full-screen view. ...



Wouldn't that mean that when you want a full screen view, which is what my example was assuming, rather than a view that only covers half the screen you should use a view controller? The content of which can be made up of 1 or more actual views. I think the main reason for this is controlling the view's display based on rotation. If you were to have a game playing in landscape and you wanted to display a view of high scores as a list in portrait so you can see more at once, you would either need your master view controller to know it's allowed to change rotation when your high score view has been added, or if it has it's own view controller it can just be set for a different orientation.

I don't think there's a right or wrong way, there are certainly advantages to each method.
1 Master ViewController is simpler and easier to implement.
Subview ViewControllers inserted programatically allow for more flexibility and potentially (very very slightly) lower overhead.


JaviCervera(Posted 2009) [#10]
@Beaker: The window still needs to be there. Now that the glView is not inside the window in Interface Builder, it is not shown automatically, that's why the statement [self.window addSubview:self.glViewController.view] I told you about is needed.

@ima747: On the code you gave Beaker, the ViewController managed a view which you added as a subview of the glView, so it's not managing the root view as the Apple documentation tells you to do.

Please trust me, I am a teacher at iPhone Programming at two universities here in Madrid (I teach a Master at the European University and a summer iPhone course at the Camilo José Cela), and while there's many things on the iPhone SDK that I don't know and have never tried, I need to know the basics to teach my students. I was given a programming course by Apple themselves and have contacted them several times with some technical questions. When something is supposed to be done in a specific way, it is better to do it like that.

If we needed to also control a subview of the root view (let's say, a UIWebView), what you do is adopt the UIViewDelegate protocol in your view controller and set it as the delegate for the web view.


Beaker(Posted 2009) [#11]
Jedive - after much head banging (and some mis-interpretation) I managed to get your guide working. Thank you.

I have my buttons attached to the View. Is this right?

I just need to work out how to hide the Cocoa layer (or individual gadgets).


JaviCervera(Posted 2009) [#12]
Yes, Beaker, you can have your buttons as subviews of the glView.

If you want some level of Cocoa-hiding, you could create a method on your Objective-C ViewController which captures the events on all buttons and simply calls a method of another C++ class.

I assume the methods you wrote for the buttons look something like:

- (IBAction)myButtonResponder;


You could add a parameter to the method, and Cocoa automatically puts the button calling the method:

- (IBAction)myButtonResponder:(UIButton*)myButton;


As the ":" specifies a parameter, Cocoa know if it has to send it or not when calling it.

I took a look at iMiniB3D but haven't played with it too much yet, but if I remember right, there was a Game C++ class or something which is supposed to be the main class for your game logic. So, let's assume that our ViewController has a pointer to the Game object, you could write a method like this:

- (IBAction)myButtonResponder:(UIButton*)myButton {
   myGame->myButtonResponder( myButton );
}


And your class can stay like that for all your projects, you just rewirte the "myButtonResponder" in your C++ class to do whatever you need.

Anyway, I haven't practise mixing C++ and Objective-C too much, since I feel much more comfortable working completely in ObjC, and that's what I use for my projects.


Beaker(Posted 2009) [#13]
Yep, good idea, seems to work.


Beaker(Posted 2009) [#14]
I never really got this sorted out. I thought I got it working using Jedives method but it caused a lot of problems that I couldn't fix. I reverted to creating my (simple) touch UI dynamically, but this seems to make things very unstable in iminiB3D. It could easily be my mistake, mixing c++ and objective-c is really messy. :)

Anyone else had any success with this kind of thing? I could really do with a usable example?

Thanks.