How do I connect an array of buttons w/i singleton? - objective-c

My appDelegate, in the method didFinishLaunchingWithOptions I load my view controller then create a singleton:
UIViewController *MainWinVC = [[p3VC alloc] init];
[[self window] setRootViewController:MainWinVC];
ansButSingleton *ansButs = [[ansButSingleton alloc] init];
ansButSingleton.h looks like this:
#import <Foundation/Foundation.h>
#interface ansButSingleton : NSObject {
// View objects:
UIButton *Ans1;
UIButton *Ans2;
UIButton *Ans3;
UIButton *Ans4;
UIButton *Ans5;
UIButton *Ans6;
UIButton *Ans7;
UIButton *Ans8;
// Other objects:
NSArray *ansButs;
}
#property (strong) UIButton *Ans1, *Ans2, *Ans3, *Ans4, *Ans5, *Ans6, *Ans7, *Ans8;
#property (strong) NSArray *ansButs;
+ (ansButSingleton *) ansButsName; // Declare class method
#end
and ansButSingleton.m like this:
#import "ansButSingleton.h"
#implementation ansButSingleton
static ansButSingleton *ansButsName;
#synthesize Ans1, Ans2, Ans3, Ans4, Ans5, Ans6, Ans7, Ans8;
#synthesize ansButs;
//////////////////// instantiate ////////////////////
+ (ansButSingleton *) ansButsName { // class method
#synchronized(self)
{
if (!ansButsName)
ansButsName = [[ansButSingleton alloc] init];
return ansButsName;
}
}
//////////////////// initialize ////////////////////
- (id)init { // class instance method
NSLog(#"Initializing answer buttons");
if (ansButsName) {
return ansButsName;
}
self = [super init];
if(self) {
// First initialze the individual buttons
self.Ans1 = [[UIButton alloc] init];
[Ans1 setTitle:#"" forState:UIControlStateNormal];
self.Ans2 = [[UIButton alloc] init];
[Ans2 setTitle:#"" forState:UIControlStateNormal];
self.Ans3 = [[UIButton alloc] init];
[Ans3 setTitle:#"" forState:UIControlStateNormal];
self.Ans4 = [[UIButton alloc] init];
[Ans4 setTitle:#"" forState:UIControlStateNormal];
self.Ans5 = [[UIButton alloc] init];
[Ans5 setTitle:#"" forState:UIControlStateNormal];
self.Ans6 = [[UIButton alloc] init];
[Ans6 setTitle:#"" forState:UIControlStateNormal];
self.Ans7 = [[UIButton alloc] init];
[Ans7 setTitle:#"" forState:UIControlStateNormal];
self.Ans8 = [[UIButton alloc] init];
[Ans8 setTitle:#"" forState:UIControlStateNormal];
// Make an array containing the objects: this is the objective-C way!
self.ansButs = [[NSArray alloc] initWithObjects: Ans1, Ans2, Ans3, Ans4, Ans5, Ans6, Ans7, Ans8, nil];
}
NSLog(#"Done initializing answer buttons");
return self;
}
#end
This builds and runs fine (it doesn't do much yet). The buttons are visible due to the successful loading of the nib. However, they are not active since I haven't connected them to the code (no IBActions).
Question: How do I connect buttons created this way to the buttons in the nib? If these were simple buttons (not an array of buttons) I would create a method and use IBAction as part of that method declaration. But this case seems a bit different. Or maybe not. If these were labels (which I also need to do later), my reading leads me to believe IBOutletCollection might work, but there is no IBActionCollection that I can see. Expert guidance needed! Thanks.
EDIT ... working with Rob to implement his ideas. My viewLoad method was copied and pasted from yours, but maybe there was something in it I needed to change?
#pragma mark - View lifecycle
- (void)loadView {
NSDictionary *externals = [NSDictionary dictionaryWithObject:[AnswerButtons answerButtons]
forKey:#"answerButtons"];
NSDictionary *nibOptions = [NSDictionary dictionaryWithObject:externals
forKey:UINibExternalObjects];
[self.nibBundle loadNibNamed:self.nibName owner:self options:nibOptions];
[[AnswerButtons answerButtons] buttonsDidLoad];
}

There are a number of ways you could do this. I'd do it by hooking up the singleton's connections in the nib. Here's how.
First, let's fix up the singleton class to match iOS programming conventions and provide the support for wiring up the button connections in the nib:
AnswerButtons.h
#import <Foundation/Foundation.h>
#interface AnswerButtons : NSObject
#property (strong, nonatomic) IBOutlet UIButton *button1, *button2, *button3, *button4, *button5, *button6, *button7, *button8;
#property (strong, nonatomic, readonly) NSArray *buttons;
+ (AnswerButtons *)answerButtons;
- (void)buttonsDidLoad;
#end
AnswerButtons.m
#import "AnswerButtons.h"
#implementation AnswerButtons
#synthesize button1 = _button1;
#synthesize button2 = _button2;
#synthesize button3 = _button3;
#synthesize button4 = _button4;
#synthesize button5 = _button5;
#synthesize button6 = _button6;
#synthesize button7 = _button7;
#synthesize button8 = _button8;
#synthesize buttons = _buttons;
+ (AnswerButtons *)answerButtons {
static AnswerButtons *singleton;
static dispatch_once_t once;
dispatch_once(&once, ^{
singleton = [[AnswerButtons alloc] init];
});
return singleton;
}
- (void)buttonsDidLoad {
_buttons = [[NSArray alloc] initWithObjects:_button1, _button2, _button3, _button4, _button5, _button6, _button7, _button8, nil];
}
#end
Note that we create the singleton in the class method the first time the singleton is requested. Don't do it in application:didFinishLaunchingWithOptions:.
Next, let's hook up the buttons in the nib:
Open the nib for the p3VC class. (You should consider changing this name to start with a capital letter and spell out ViewController or just Controller.)
Drag an “External Object” from the Object Library into the nib.
In the Identity Inspector, set the External Object's custom class to AnswerButtons.
In the Attributes Inspector, set its External Object Identifier to answerButtons.
Control-drag from the Answer Buttons object to each of the eight buttons, connecting the button to the appropriate outlet.
Finally, we need to provide the AnswerButtons singleton to the nib loader when we load the nib. Edit p3VC.m and give it a loadView method:
p3VC.m
- (void)loadView {
NSDictionary *externals = [NSDictionary dictionaryWithObject:[AnswerButtons answerButtons]
forKey:#"answerButtons"];
NSDictionary *nibOptions = [NSDictionary dictionaryWithObject:externals
forKey:UINibExternalObjects];
[self.nibBundle loadNibNamed:self.nibName owner:self options:nibOptions];
[[AnswerButtons answerButtons] buttonsDidLoad];
}
With this approach, you can also create IBAction methods in the AnswerButtons class and use the nib to connect the buttons to those actions.

Since UIButton inherits from UIControl you can use the method:
[yourButton1 addTarget:yourObject action:#selector(yourMethod) forControlEvents:UIControlEventTouchUpInside];
Hook that in the loop where the buttons are created and you are good to go. If you are adding the buttons from interface builder then you need to hook an IBOutlet for each. Example: In your view controller init:
self.Ans1 = [[UIButton alloc] init];
[Ans1 setTitle:#"" forState:UIControlStateNormal];
//set the action
[Ans1 addTarget:self action:#selector(yourMethod) forControlEvents:UIControlEventTouchUpInside];
Another way would be to create the IBAction method for each button and hook that method to the method in the singleton, which in my opinion is much more clear.
Example: in your view controller:
-(IBAction) button1Press:(id)sender{
[yourSingleton button1Press];
}

To connect the buttons to interface builder, you'd have to place "IBOutlet" before the UIButton in each one of your variable declarations.
IBOutlet UIButton * Ans1;
Then in interface builder, you can right click and drag from view controller onto the button and select the right button. Then if you wanted to have each button perform a method, you should declare an IBAction method in your .h file:
-(IBAction)doButtonStuff:(id)sender;
Then to hook up the act to each button in interface builder, go to interface builder and right click and drag from the button to view controller and select what method you want to associate it with. Typically using interface builder is quick and easy but if you want to do some extra behind the scenes stuff, you can use the code whitelion posted.
Also, just a side note, to use less code when setting up your buttons, you can do a for each loop.
for (UIButton * button in ansButs) {
self.button = [[UIButton alloc] init];
[button setTitle:#"" forState:UIControlStateNormal];
}
This is a lot less code!!! You'd have to declare the array before this code and include all of the buttons in it.

Related

UIViewController Retaining in ARC

I have a subclass of UIViewController -> MyPopUpViewController
#protocol MyPopUpViewController Delegate;
#interface MyPopUpViewController : UIViewController
{
}
#property (nonatomic, strong) id <MyPopUpViewControllerDelegate> delegate;
-(IBAction) buttonPressed:(id)sender;
#end
#protocol MyPopUpViewControllerDelegate
-(void) popupButtonPressed: (MyPopUpViewController*)controller;
#end
I cannot have this MyPopUpViewController as an instance variable because this comes externally, and there could be many and multiple of these popups can be up. So far I tried this, and it crashes on the delegate call due to not being retained:
MyMainViewController:
-(void)externalNotificationReceived: (NSString*) sentMessage
{
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[self.view addSubview:popupView.view];
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
}
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
NSLog(#"Popup Delegate Called");
[controller.view removeFromSuperview];
controller.delegate = nil;
controller = nil;
}
Once the popup comes up, and when the ok button is tapped, it crashes and never gets to that NSLog. How can I change
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
..so it would retain without making it an instance variable?
Thanks in advance.
You should be doing proper view controller containment by calling addChildViewController:.
- (void)externalNotificationReceived: (NSString*) sentMessage {
MyPopUpViewController *popupView = [[MyPopUpViewController alloc] init];
popupView.delegate = self;
[popupView setInfo :sentMessage :#"View" :#"Okay"];
popupView.view.frame = CGRectMake(0, -568, 320, 568);
popupView.view.center = self.view.center;
[self addChildViewController:popupView];
[self.view addSubview:popupView.view];
[popupView didMoveToParentViewController:self];
}
This will keep a proper reference to the view controller as well as properly pass various view controller events. Read about this in the docs for UIViewController and the "View Controller Programming Guide for iOS".
BTW - you should name your methods better. Example:
popupButtonPressed::
should be named:
popupButtonPressed:buttonNumber:
Usually delegates are weak-referenced instead of strong. I, myself, would name it something else as to not confuse other people.
Also, the following bit of code will have no effect:
-(void)popupButtonPressed:(MyPopUpViewController *)controller :(int)sentButtonNumber
{
...
controller = nil;
}
the controller would be released (set to nil) automatically at the end of the scope.

iOS - Implementation of MVC-Pattern without Interface Builder

I'm creating a little MVC-sample-project as an iPhone-application in XCode, which is built completely with code and therefore doesn't use Interface Builder. First I'd like to show you the code I have so far.
Controller
The controller instantiates the model and the view and also contains a function which demonstrates the independency between model and view:
ViewController.h
#import <UIKit/UIKit.h>
#import "MainView.h"
#import "ProjectModel.h"
#interface ViewController : UIViewController
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
{
ProjectModel *model;
MainView *myMainView;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
model = [[ProjectModel alloc] init];
myMainView = [[MainView alloc] initWithFrame:CGRectMake(0, 0, 320, 468)];
[self.view addSubview:myMainView];
//test function to illustrate that view and model are independent
[self calculate];
}
- (void)calculate
{
int result = [model operationWithNumber:3 andAnotherNumber:5];
[myMainView showResult:result];
}
#end
Model
The class ProjectModel is responsible for the model of the project and is in this example for the sake of simplicity only responsible for summing up two numbers:
ProjectModel.h
#import <Foundation/Foundation.h>
#interface ProjectModel : NSObject
-(int)operationWithNumber:(int)number1 andAnotherNumber:(int)number2;
#end
ProjectModel.m
#import "ProjectModel.h"
#implementation ProjectModel
-(int)operationWithNumber:(int)number1 andAnotherNumber:(int)number2
{
return (number1 + number2);
}
#end
View
The view-class creates all the elements of the view and contains a function which displays the result of a calculation in a label.
MainView.h
#import <UIKit/UIKit.h>
#interface MainView : UIView
{
UILabel *lblResult;
}
- (void)showResult:(int)result;
#end
MainView.m
#import "MainView.h"
#implementation MainView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
UILabel *lblTitle = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 280, 50)];
lblTitle.text = #"This is my View";
[self addSubview:lblTitle];
lblResult = [[UILabel alloc] initWithFrame:CGRectMake(20, 200, 280, 50)];
lblResult.text = #"Result will be displayed here.";
[self addSubview:lblResult];
}
return self;
}
- (void)showResult:(int)result
{
lblResult.text = [NSString stringWithFormat:#"Resultat: %d", result];
}
#end
My Question:
I hope you understood the code so far. Based on the code above, I'd like to implement a button in the view-class which should calculate and display two numbers when the users clicks this button. Thus when the user clicks the button, the calculate-function in the ViewController should be called. I created a button with the following code in MainView.m:
UIButton *btnCalculate = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnCalculate setFrame:CGRectMake(20, 90, 280, 50)];
[btnCalculate setTitle:#"Calculate" forState:UIControlStateNormal];
[btnCalculate addTarget:self action:#selector(calculate:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:btnCalculate];
The problem is this line in the code above:
[btnCalculate addTarget:self action:#selector(calculate:) forControlEvents:UIControlEventTouchUpInside];
how can I add a function from the controller as the action of the button. I know that the target should not be self, since the function should be called in the ViewController, but I have no idea how I could do that. Could anyone help me and tell me how I can solve this problem?
The only solution that I can see right now is that the complete GUI is created in the ViewController directly. But I don't think that this is a nice solution, since the main purpose of MVC is to avoid the mix of controller and view code in the same class.
Also I'm wondering whether this code generally conforms to the MVC-pattern propagated by Apple, since I'm pretty new to this design-pattern. I'd really appreciate a short feedback for this code.
Don't set the target in the view class. For MVC, the view should not explicitly know about the controller. The view class should have a public property to expose the button and then the controller can update the button to add the target.
Other than that it looks like you understand the point of MVC.
This line does confuse a bit [self.view addSubview:view]; as it appears to be trying to add the view as a subview of itself...
The MVC pattern is a nice thing, but I don't think it is necessary for a program like yours to have that degree of separation. Also the button can be in the view class since it is a part of that pattern along with calculate function.

(Target: Object) not working for setting UIButton from outside viewController

I can create the UIButton.
But the callback: target: object won't work within the current object. ??
I can only do "self.viewController" object, and can't do "self" for current object.
thx
#import <Foundation/Foundation.h>
#import "ViewController.h"
#class ViewController;
#interface CGuiSetup : NSObject
{
#public
ViewController *viewController;
}
- (void) Setup;
- (void) ButtonRespond:(UIButton*) btn;
#end
#import "CGuiSetup.h"
#implementation CGuiSetup
- (void) ButtonRespond:(UIButton*) btn
{
NSLog(#"ButtonRespond");
}
- (void) Setup
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
// But I want to call the following in the CGuiSetup object (self) instead... but it crashes out if I leave it as just "self"
[btn addTarget:self.viewController action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //works: for "self.viewController" if I put ButtonRespond in the ViewController
[btn addTarget:self action:#selector(ButtonRespond:)
forControlEvents:UIControlEventTouchUpInside]; //fails: for "self"
[self.viewController.view addSubview:btn];
}
#end
#import <UIKit/UIKit.h>
#import "CGuiSetup.h"
#class CGuiSetup;
#interface ViewController : UIViewController
{
CGuiSetup *guiSetup; //<---- had to take this out of the "viewDidLoad" method
}
#end
- (void)viewDidLoad
{
[super viewDidLoad];
guiSetup = [CGuiSetup alloc];
guiSetup->viewController = self;
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn setFrame:CGRectMake(10,10,100,100)];
[btn addTarget:guiSetup action:#selector(ButtonRespond:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
If you're using ARC, does any object have a retaining reference to CGuiSetup? I sounds like CGuiSetup is instantiated, creates (or maybe receives from another object) the viewController, adds the button to it and then gives the view controller to another object (perhaps by pushing it on a navController or setting it to be the root controller of the app)? Whatever the case is, CGuiSetup it being dealloc'd and the button is trying to send a message to an object that's already been destroyed. How/where is CGuiSetup created? What object retains a reference to it? That's probably where your problem is.
If you don't understand what retain/release is and/or you don't know what ARC is, you need to read Apple's memory management guide: https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html
That's probably because your object is destroyed, while _viewController still has retain count greater than 0 (so it's not destroyed). Balance you're retain/release count.

Calling SEL from parentViewController in iOS

I have a ViewController in which I create a Modal ViewController. I have a SEL value in my modal that I set when it is being instantiated from the parent.
setDateViewController.selectorName = #selector(myMethod:);
In my modal I am trying to call this SEL like:
[[self parentViewController] performSelector:self.selectorName withObject:selectedDate afterDelay:.5];
{selectedDate} is obviously a value from my modal.
I don't get any errors or stack, however, this SEL (method) on my parent is never being called. For some reason I think this should work, but something tells me I'm way off track.
Thanks.
I guess [self parentviewcontroller] is not returning anything.
Try UiviewController* v = [self parentviewcontroller]; and check if is nil. Most probably it should be nil. Else if its was pointing to another object of different class then it would have crashed. Please do one thing. YOu should set bot the object and the methd you need to call. IT will solve any issues if it has any.
setDateViewController.selectorDelegate = self;
setDateViewController.selectorName = #selector(myMethod:);
call like this from parent class. So you can dynamically specify the method and the object you want to call gives more flexibility.
and use,
[selectorDelegate performSelector:self.selectorName withObject:selectedDate afterDelay:.5];
this should solve any issues.
Perhaps you would consider added a delegate protocol to your modal that will allow it to call the method on the parent.
Quick (untested) example:
// MyController.h
#protocol MyControllerDelegate;
#interface MyController : UIViewController
{
id<MyControllerDelegate> delegate;
}
#end
#protocol MyControllerDelegate <NSObject>
- (void)methodToCall:(id)sender;
#end
// MyControler.m
#implementation MyController
- (void) loadView
{
[super loadView];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
btn.frame = CGRectMake(20.0f, 20.0f, 50.0f, 30.0f);
[btn setTitle:#"blah" forState:UIControlStateNormal];
[btn addTarget:self.delegate
action:#selector(methodToCall:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:btn];
}
#end
// ParentController.h
#interface ParentController : UIViewController<MyControllerDelegate>
{
}
#end
// ParentController.m
#implementation ParentController
- (void)methodToCall:(id)sender
{
NSLog(#"HERE");
}
#end
Just make sure when you are creating your modal controller you set it's delegate to self on the parent:
MyController *controller = [[MyController alloc] init];
controller.delegate = self;
[self presentModalViewController:controller animated:YES];

Create UIImageView in object

I am trying to create a UIImageView programmatically in a method of a object.
-(void) createApple
{
CGRect myImageRect = CGRectMake(100, 100, 64, 64);
image = [[UIImageView alloc] initWithFrame:myImageRect];
[image setImage:[UIImage imageNamed:#"Apple.png"]];
[self.view addSubview:image];
[image release];
}
when I use this code in the ViewController.m it works fine but when I use it in my Apple object I get an error:request for member 'view' in something not a structure or union.
What do I use instead of self.view???
Sorry if I'm repeating posts. I saw a similar question to this but wasn't sure if it was exactly the same situation.
If I got your question correctly you have an interface like this
#interface Apple: NSObject
#end
If you want to create an UIImageView using NSObject methods try this
//first declare a method of UIImageView type
#interface Apple: NSObject
{
}
-(UIImageView *)ImageView;
#end
now come in your Apple.m file
#implementation Apple
-(UIImageView *)ImageView
{
UIImageView *imgView = [UIImageView alloc]initWithFrame:CGRectMake(100, 100, 64, 64)];
imgView setImage:[UIImage imageNamed:#"Apple.png"];
return imgView;
}
#end
now call this method where you want to show your image, in your ViewController.m file you can call it like this- You just have to import your object class first.
#import "ViewController.h"
#import "Apple.h"
#implementation ViewController
-(Void)ViewDidLoad
{
Apple *appleObject = [Apple alloc]init];
UIImageView *imageViewOfViewController = [appleObject ImageView];
[self.view addSubView:imageViewOfViewController];
}
#end
I show you imageview method in viewDidLoad method you can call it any where like this.
The line causing the error would be:
[self.view addSubview:image];
If your Apple object does not extend UIViewController then there will be so self.view property on it, unless you have defined one yourself.
As to what to use instead, that is difficult to say without knowing specifically what you are trying to do.
However, assuming that you want your Apple class to display something on the screen, then probably what you want to do is update Apple so that it extends either UIViewController or (perhaps) UIView. Then in the first case you can use self.view, and in the second case you can just do [self addSubview: image];.
Because "view" is a property of UIViewController and your "apple object" doesn’t have one.
The problem is, you're not calling this method inside UIViewController. Personally I'd do it slightly other way:
- (UIView*) newAppleView
{
CGRect myImageRect = CGRectMake(100, 100, 64, 64);
UIImageView *imageView;
imageView = [[UIImageView alloc] initWithFrame:myImageRect];
[imageView setImage:[UIImage imageNamed:#"Apple.png"]];
return [imageView autorelease];
}
Assuming the newAppleView method is implemented in your UIViewController, you can easly add new apple:
[self.view addSubview:[self newAppleView]];