Making a custom UISegmentControl(sort of) - iphone

I've got this mock up:
As you can see, it's a sort of navigation-menu. It's functionality should be the same as a segmented control, and i am going to change the tableView based on the item active.
What would be the easiest way to implement this?
I have started makin my UIView-subclass, but found out that i had to then make a delegate, watch for tap-events and stuff.
Is this the best way to do it? Should i subclass UISegmentedControl?
Any other advice?
Please, point me in the right direction. I feel confident in Obj-c, but making these kinds of stuff makes my mind goes crazy.

Conceptually, UISegmentedControl seems like a good choice for this, but I don't think it's quite flexible enough to create the effect you're going for here.
Have you considered putting three UIButton controls inside a custom view? You can customize the images for each button using setBackgroundImage:forState: to get the border style in your mockup. Set the selected property of the pressed button to YES, and UIButton will handle the drawing for you.
You can set up an action method to detect which button was pressed by calling
[button addTarget:self action:#selector(nameOfMethodToHandleButtonPress) forControlEvents:UIControlEventTouchUpInside])]
A delegate is just any class that conforms to a protocol you create. So you would create a delegate protocol in your header like this:
#class MyControl; // this is a forward reference to your class, as this must come before the class #interface definition in the header file
#protocol MyControlDelegate <NSObject>
#optional
- (void)myControl:(MyControl *)control didSelectButton:(int)buttonIndex; // replace this method with whatever makes sense for your control
#end
And the delegate is just a property in your MyControl class:
#property (nonatomic, assign) id <MyControlDelegate> delegate; // you use 'assign' instead of 'retain' to prevent retain cycles
And in your button press handlers, for example:
- (void)methodThatHandlesButtonPress { // this is the method you set up in the first code example with addTarget:action:forCotnrolEvents:
if ([self.delegate respondsToSelector:#selector(myControl:didSelectButton:)])
[self.delegate myControl:self didSelectButton:0]; // replace as appropriate
}
Now, you just have to have the view controller that contains your control adopt the protocol:
#interface MyViewController : UIViewController <MyControlDelegate> { // etc...
And implement the method:
- (void)myControl:(MyControl *)control didSelectButton:(int)buttonIndex {
// handle the button press as appropriate
}

Related

Subclass a Delegate?

I have a class called ToolbarView which is a subclass of UIView and basically creates a UIView that has a disappearing / reappearing UIToolbar on top. I also have a subclass of ToolbarView called DraggableToolbarView which enables the user to drag the view around the screen.
I need to create a delegate for ToolbarView so it can notify another object / class of when the toolbar reappears and disappears. I also need to create a delegate for DraggableToolbarView so I can notify another object / class when the view is dragged.
Currently, I have create a separate delegate for each, but I am wondering if there is a better pattern for this? Maybe implement one delegate for ToolbarView, and list the delegate methods from DraggableToolbarView as optional? Or is there a way to subclass a delegate?
What is the best / cleanest way to accomplish this?
If you create a protocol for your delegate methods (always a good idea anyway), you can have another protocol adopt the first. That sets up an inheritance-like relationship:
#protocol ToolbarViewDelegate
// some methods
#end
#protocol DraggableToolbarViewDelegate <ToolBarViewDelegate>
// additional methods
#end
Yes, you can have inheriting protocols:
#protocol Proto1
#reqired
-(void) somethingHappened:(id) sender;
#optional
-(void) somethingElseHappened:(id) sender;
#end
#protocol Proto2<Proto1>
// this now contains all of the method signatures found in proto1, with the addition of new ones!
-(void) somethingSpecialHappened:(id) sender;
#end
I think you're doing it right.
Consider UITextView which is a subclass of UIScrollView. Each has its own delegate protocol that's responsible for reacting to a specific set of messages. As long as you think of visibility and dragging as separate concerns, allowing different objects to handle their delegation seems logical.

Same UIView subclass in several UIViewController subclasses?

I have several UIViewControllers. I would like to use the same UIView subclass (which goes on top of the existing UIViewController's view) in all of them. Is this possible using Interface Builder?
I mean, I would like to be able to drag a UIView onto each UIViewController's view, and rename the class of this dragged UIView to CustomView, and all the elements within the CustomView would show up... is this possible?
Based on your question and your response to highlycaffeinated — whose answer is correct but I think may be slightly askew from what you're asking — I think you want to be able to design a view graphically within Interface Builder (so, you haven't written a custom UIView subclass, you've just arranged some UIViews in a certain way and so that they're all children of another view), then embed it into several view controllers via some sort of indirect reference, so that you're not copying and pasting the same user interface elements and if you make a change in one place, that change then takes effect everywhere?
As far as I'm aware, there's no built-in facility in Interface Builder or Xcode 4 for achieving that. XIBs are pure data and UIViews don't have the smarts to handle an out-of-file reference.
What you can do is design the view you want to use in one XIB, called say ReusableView.xib, then write a custom UIView subclass that looks something like:
#implementation ReusableViewTemplate
- (id)initWithCoder:(NSCoder *)aDecoder
{
// initialise ourselves normally
self = [super initWithCoder:aDecoder];
if(self)
{
// load everything in the XIB we created
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"ReusableView" owner:self options:nil];
// actually, we know there's only one thing in it, which is the
// view we want to appear within this one
[self addSubview:[objects objectAtIndex:0]];
}
return self;
}
#end
Then, in your NIBs put in a UIView where you want the reusable view to go, and set the 'class' to 'ReusableViewTemplate' or whatever you called it.
If you open the ReusableView XIB and set the type of the parent view to ReusableViewTemplate, then you can wire up any UIControls (such as buttons or switches) to connect to there. You'll probably want to define some custom protocol for your reusable view template and catch viewDidLoad in any view controllers that use the reusable view in order to set an appropriate delegate.
EDIT: further thoughts on this. I've created an example project (currently at a generic file sharing site, so may not survive forever) with a class ReusableView that, for the purpose of example contains a segment view and a button, and looks like this:
#implementation ReusableView
/*
initWithCoder loads the relevant XIB and adds its
only object, which is a UIView, as a subview of this
one. If you don't like the double hierachy, you
could just have a list of views in the XIB and
addSubviews:, but then it'd much more difficult to
edit the thing graphically. You could strip the top
view programmatically, but this is just a simple
example, so...
*/
- (id)initWithCoder:(NSCoder *)aDecoder
{
// initialise ourselves normally
self = [super initWithCoder:aDecoder];
if(self)
{
// load everything in the XIB we created
NSArray *objects = [[NSBundle mainBundle]
loadNibNamed:#"ReusableView"
owner:self
options:nil];
// actually, we know there's only one thing in it, which is the
// view we want to appear within this one
[self addSubview:[objects objectAtIndex:0]];
}
return self;
}
#synthesize delegate;
#synthesize segmentedControl;
#synthesize button;
/*
NSObject contains machinery to deal with the possibility
that a class may be sent a selector to which it doesn't
respond.
As of iOS 4, forwardingTargetForSelector: can be used to
nominate an alternative target for the selector quickly.
In previous versions of iOS, or in iOS 4 if you don't
respond to forwardingTargetForSelector:, you may take
delivery of the problematic invocation and deal with it
yourself.
Dealing with the invocation costs more than providing
a forwarding target for the selector, so its worth having
both.
If you're only targeting iOS 4 or above, you needn't
keep the implementation of forwardInvocation: below.
What we're doing is declaring a bunch of IBActions so
that we can wire changes up to them in Interface Builder.
By failing to implement them and providing the delegate
as the thing to talk to for any selectors we don't know,
we're allowing those wirings to be as though assigned
to the delegate.
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
return delegate;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
[anInvocation setTarget:delegate];
[anInvocation invoke];
}
#end
With interface:
#interface ReusableView : UIView
{
IBOutlet id delegate;
IBOutlet UISegmentedControl *segmentedControl;
IBOutlet UIButton *button;
}
#property (nonatomic, assign) id delegate;
#property (nonatomic, assign) UISegmentedControl *segmentedControl;
#property (nonatomic, assign) UIButton *button;
/*
NB: we're not actually going to implement these. We're
declaring them for the benefit of Interface Builder / Xcode 4.
What we'll actually do is, any time we receive a selector
we don't implement, hand it off to the delegate. So it's a
quick way of avoiding writing any glue code to pass messages
from this little reusable view to its delegate.
A better alternative could define a formal protocol that
forwards both the changed control and self from the
reusable view to its delegate. But that's obvious and
verbose, so has been omitted for the purposes of example.
The implementation as stands will generate compiler warnings,
but such is life. To get rid of the warnings, comment out
the two following lines, but only AFTER you've wired
everything up in Interface Builder / Xcode 4. They're left
uncommented here to help draw attention to the point about
selector/invocation forwarding that you'll see in the
#implementation.
!!!!!!!!!!!!!!!
HENCE:
delegates MUST implement the following methods.
!!!!!!!!!!!!!!!
We could work around that by checking at runtime whether
the actual delegate implements them and forwarding to
a dummy object that implements them to do nothing otherwise,
but that's slightly beyond the point of the example.
*/
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;
#end
Net effect is that if a view controller has a UIView of type ReusableView within a XIB, it gets the contents of ReusableVew.xib inserted at runtime. If it wires itself up as the delegate of ReusableView within Interface Builder / Xcode 4 and implements:
- (IBAction)reusableViewSegmentControlDidChange:(id)sender;
- (IBAction)reusableViewButtonWasPressed:(id)sender;
Then it gets the messages from the embedded views.
This is achieved very simply and very neatly in Objective-C by using NSObject's inherent ability to forward selectors (as of iOS 4) or invocations (in earlier versions, at a greater cost) that it doesn't implement rather than allow an exception to occur.
Yes, it's possible. Just as you could have (for instance) multiple UIViewControllers in your project, each with a UIImageView as a view, you can do the same with your own subclasses of UIView.

How to access variables of a ViewController in a subclass?

I guess this is basic, but I can't get my head around this.
I used to have only one ViewController in which all my variables were defined, e.g. an UITextView named myTextView. I also had methods in this ViewController for handling events that relate to myTextView, such as - ()hideKeyboard { // do something with myTextView or - (void)keyboardWillShow:(NSNotification *)notification { // do something with myTextView.
As my program became bigger and bigger, I thought about using subclasses, especially for other views. So I started a subclass, eg. mySubClass.h and mySubClass.m, in which I had another UITextView (for argument's sake myOtherTextView). In order to incorporate mySubClass, I #imported it into my ViewController and added a #class mySubClass; and could then produce instances of this class so as to use it in my App.
So far so good. As you can imagine, all the nice methods I defined in my ViewController for what should happen when an UITextView is edited (such as hiding keyboard etc.) didn't work for the UITextView in mySubClass.
It was then suggested to me that I should make another class in which I had all the keyboard events and subclass my ViewController and mySubView to it:
#interface ViewController : MyKeyboardEventsViewController
Now, the problem I am seeing is that I won't be able to access all the views, textviews, textfields etc. that I have created in my ViewController (e.g. myTextView which I mentioned earlier).
How can I achieve that all the variables that I have defined in my ViewController will also be available for MyKeyboardEventsViewController? Or is there another way to handle this?
Basically, I don't get how MyKeyboardEventsViewController will be able to access variables in my ViewController which it will need (e.g. the UITextView in question, or the accessoryView which will pop up etc. etc.).
Any suggestions would be very much welcome.
Example:
Class A contains a ivar UITextField textField
Class B subclasses Class A and thus it already contains ivar textField
Note: it's not the other way around. Class A does not "see" what ever is created in Class B.
When ever you subclass a class you give your new class the same ivars end methods of that subclassed class.
I hope this is what you were asking for.
EDIT
So for your example I would do the follwing:
Create a class "MyUIKeybordEventResponder"
Implement all the responder methods like - (BOOL)textFieldShouldReturn:(UITextField *)textField
Subclass your ViewController from "MyUIKeybordEventResponder"
Note method textFieldSHouldReturn has a parameter UITextField so it knows which textfield was pressed. So in a way it receives your textField from the subclass.
If I'm understanding this correctly, you have a UIViewController with MyKeyboardEventsViewController as an instance variable and you want to communicate between the two? If that is the case, one option would be to create a protocol.
#protocol MyKeyboardDelegate
- (void)closeAccessoryView;
#end
(Note - make whatever methods in the protocol that you need, this is simply an example)
In your MyKeyboardEventsViewController you then include the protocol file, and create an ivar
id <MyKeyboardDelegate> delegate;
Also make it a property and synthesize it.
Whatever class that is going to create the keyboardviewcontroller should delcare themselves as conforming to the protocol.
#interface MyViewController : UIViewController <MyKeyboardDelegate>
...
#end
When you create the MyKeyboardEventsViewController, set the delegate.
MyKeyboardEventsViewController *eventsVC = [[MyKeyboardEventsViewController alloc] init];
[eventsVC setDelegate:self];
Now just implement the delegate method and perform whatever action that is necessary.

Selector sent to parent instead of self

Typically when creating an action from something like a button you have this
[button addTarget:self action:#selector(method:)...
I can guess if I want to send the action to another class but what if I want to send it to the parent? I thought super might work but that gives a warning.
EDIT:And super crashes the App
Cheers for any help.
Let's assume you have the following classes:
#interface Base {
}
- (IBAction)method:(id)sender;
#end
#interface Derived {
}
#end
If Derived doesn't have it's own implementation of method: then your standard setup with self and #selector(method:) will do what you want.
If Derived DOES have it's own implementation of method: then you have no choice but to add your own method in derived that doesn't nothing but forward the call up to the superclass implementation.
- (void)callMethodOnSuper:(id)sender {
[super method:sender];
}
Then use #selector(callMethodOnSuper:) instead. You cannot just supply super as the target.
EDIT: The following is an update based on the comment below actually clarifying what the OP wants to do.
Just to make sure I'm clear, I'll restate what your seems to be your situation. You have a view controller, say MyViewContoller, whose view has three subviews. One of those subviews, a MyCustomView, has some UIButton subviews.
My suggestion is as follows:
#interface MyViewController {
}
#end
#interface MyCustomView {
UIButton* button1;
UIButton* button2;
}
#property (nonatomic,readonly,retain) UIButton* button1;
#property (nonatomic,readonly,retain) UIButton* button2;
#end
When your view controller is building it's view:
[myCustomView.button1 addTarget:self action:#selector(method:)...
[myCustomView.button1 addTarget:self action:#selector(method:)...
The way I understand your question, you are trying to implement something that does exactly the same as the delegate pattern, but the way you are going around it you will not get any compiler hints or error if the super class does not implement the correct method for the target, it will simply crash when you run it.
It might seem as a good idea in regards of encapsulation, but I think it would be hard to debug if you reuse the view component elsewhere.
The only case I use the approach of trying to message something other than "self" is where a ViewController has X views and these views needs each other and needs to react to each others actions. Still, if I have implemented a viewController as delegate for the views, I would just let the viewController change the other views to whatever state they should go to.
(hope I didn't misunderstand the question)

iPhone: Switching Views From Outside Root Controller

I am using a UINavigationController to switch between views. What I would like is for each view to have the ability to control when it is swapped out for another view by having buttons within the view. All of the samples I've seen thus far have placed buttons on a toolbar, which is located on the root view containing the Switch View Controller rather than the views, them self. Is it possible to do what I want? I can't figure how to wire up the connection back to the UINavigationController.
I'm having a difficult time wording this, so please feel free to let me know if you need additional clarification.
Read about delegates. Delegates are a common method to signal stuff from objects to their "parents" or any other objects.
You should have a "delegate" property (can really be called anything, this is just a convention) on your child views. You can have buttons in your child views.
You declare the delegate like this:
interface ChildView : UIViewController {
id delegate;
}
#property (assign) id delegate;
implementation ChildView
#synthesize delegate;
Then, when you set up your child views inside your UINavigationController, you do:
ChildView *childView = [[ChildView alloc] init...]
childView.delegate = self;
Inside your child view, you have a button method:
- (IBAction) didPressButton:(id)sender {
[self.delegate didPressButtonToSwapView];
}
Inside your UINavigationController, you have a method:
- (void) didPressButtonToSwapView {
[self popViewController]; // use the right names, I made these up :)
[self pushAnotherViewController];
}
You should also read about protocols which would make the above code more robust and would help you make sure you only call the right methods on delegate, but I did not want to complicate this example.
EDIT: yes, the cleanest way to get rid of the warning is to use a protocol. Just put this in a separate .h file:
#protocol SwitchingDelegate
- (void) didPressButtonToSwapView;
#end
Include this .h in the UINavController header, and say the UINavController implements the protocol:
#interface MyNav: UINavController <SwitchingDelegate> { ...
Implement the method in the implementation (you don't need anything more in the interface).
In your ChildView, say that the delegate must implement the protocol: change all the declarations to:
id<SwitchingDelegate> delegate;
The compiler then helps you by checking whether the delegate objects really implement the protocol. You should not get any warnings when you have completed all of this correctly.