I am trying to make a protocol for a detail view for a tableView. The detail view has a question and then an answer. If I get the answer correct, it will set an integer to increase by 1 in a protocol method.
I am new to protocols and I don't understand what I am doing wrong.
Code
DetailViewController.h
Where the protocol is made
#import "Question.h"
#protocol DetailQuestionViewControllerDelegate <NSObject>
-(void)questionsCorrectHasChangedTo:(int)questionNumberChanged;
#end
#interface DetailQuestionViewController : UIViewController
#property (nonatomic, strong) Question *selectedQuestion;
#property (strong, nonatomic) IBOutlet UILabel *questionLabel;
#property (strong, nonatomic) IBOutlet UITextField *answerField;
#property (strong, nonatomic) IBOutlet UILabel *correctLabel;
#property (nonatomic,strong) id <DetailQuestionViewControllerDelegate> delegate;
#property (assign, nonatomic) int questionsCorrect;
DetailViewController.m
#implementation DetailQuestionViewController
#synthesize questionLabel;
#synthesize answerField;
#synthesize correctLabel;
#synthesize selectedQuestion;
#synthesize questionsCorrect;
#synthesize delegate;
- (void)viewDidLoad
{
[super viewDidLoad];
// Sets the questionLabel to the question we put in the array
self.questionLabel.text = [selectedQuestion questionName];
// Sets the navigation title to the rowName we put in the array
self.navigationItem.title = [selectedQuestion questionRowName];
NSLog(#"The question's answer for the question you selected is %#", [selectedQuestion questionAnswer]);
}
- (IBAction)checkAnswer:(UITextField *)sender
{
if ([[selectedQuestion questionAnswer] caseInsensitiveCompare:answerField.text] == NSOrderedSame)
{
// Show the correct label
[correctLabel setHidden:NO];
correctLabel.text = #"Correct!";
correctLabel.textColor = [UIColor greenColor];
*questionsCorrect = 1;
NSLog(#"questionsCorrect int is %d", questionsCorrect);
[self.delegate questionsCorrectHasChangedTo:questionsCorrect];*
}
else
{
// Show the incorrect label
[correctLabel setHidden:NO];
correctLabel.text = #"Incorrect";
correctLabel.textColor = [UIColor redColor];
}
// Erase the text in the answerField
answerField.text = #"";
}
ScoreViewController.h
Now here is my ScoreView which will be acsessing the delegate
#import <UIKit/UIKit.h>
#import "DetailQuestionViewController.h"
#interface ScoreViewController : UIViewController *<DetailQuestionViewControllerDelegate>*
#property (strong, nonatomic) IBOutlet UILabel *scoreLabel;
- (IBAction)resetButtonClicked:(UIButton *)sender;
-(void)checkScore;
#end
ScoreViewController.m
#import "ScoreViewController.h"
#import "DetailQuestionViewController.h"
#interface ScoreViewController ()
#end
#implementation ScoreViewController
#synthesize scoreLabel;
- (void)viewDidLoad
{
[super viewDidLoad];
*DetailQuestionViewController *dqvc = [[DetailQuestionViewController alloc] init];
dqvc.delegate = self;*
}
-(void)viewWillAppear:(BOOL)animated
{
[self checkScore];
}
-(void)checkScore
{
}
- (IBAction)resetButtonClicked:(UIButton *)sender
{
}
#pragma mark - DetailQuestionViewControllerDelegate -
*-(void)questionsCorrectHasChangedTo:(int)questionNumberChanged*
{
//set the textlabel text value to the number of questions correct
NSLog(#"questionsNumberChanged is %i", questionNumberChanged);
scoreLabel.text = [NSString stringWithFormat:#"You answered %d questions correctly",questionNumberChanged];
}
#end
The label is never updating for some reason.
Sorry for making the question so long, tried to be very specific.
I'm guessing somewhat here as you talk about increasing a value in a protocol method, yet you don't have a single + or ++ anywhere... You also have quite a few *'s sprinkled in strange places in your sample code, it is unclear whether these are typos, intended as emphasis, or intended to be pointer indirection.
So you have a property questionsCorrect in your DetailQuestionViewController class so let's assume this is the class you expect to own the counter (we'll skip that this is a view and not a model class...). If this is the idea then lines:
*questionsCorrect = 1;
NSLog(#"questionsCorrect int is %d", questionsCorrect);
[self.delegate questionsCorrectHasChangedTo:questionsCorrect];*
should be:
self.questionsCorrect++; // increment the counter
NSLog(#"questionsCorrect int is %d", self.questionsCorrect);
[self.delegate questionsCorrectHasChangedTo:self.questionsCorrect];
(you can also declare the instance variable questionsCorrect yourself and drop the use of self. above - whichever you prefer)
Now just go through and remove the other cases of extra *'s if they are in your code as well as the sample above and you'll be a bit closer to your goal.
If alternatively you wish ScoreViewController to own the counter then you need to declare it there and provide a method to increment and display it.
HTH
Related
In Objective-C, I am trying to make a NSTextField, when clicked, open a sheet with a NSDatePicker that slides out under the text field. You select a date which closes the sheet and populates the NSTextField with the date chosen.
I have found this article on how to use a protocol to do this in Swift. http://www.knowstack.com/swift-nsdatepicker-sample-code/#comment-20440
But when I convert it to Objective-C I have a few issues.
The first time I click my button to trigger the sheet, the sheet appears in the middle of the screen, ignoring the event:
-(NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect {
When I select a date, the textfield in the main xib is updated with the selection so the protocol part is working but the sheet remains unresponsive on screen.
If I click the button a second time, the unresponsive sheet closes and reappears under the NSTextField and dismisses itself when I choose a date. This is the expected behaviour.
My question is, why does this not work the first time I click the button but only works the second time?
Here is the code:
#import <Cocoa/Cocoa.h>
#protocol DatePickerProtocol
#required
-(void) selectedDate:(NSDate *)date;
#optional
#end
#interface datePickerWindowController : NSWindowController {
id delegate;
}
-(void)setDelegate:(id)newDelegate;
#end
#import "datePickerWindowController.h"
#interface datePickerWindowController ()
#property (weak) IBOutlet NSDatePicker *datePicker;
#end
#implementation datePickerWindowController
- (void)windowDidLoad {
[super windowDidLoad];
self.datePicker.dateValue = [NSDate date];
}
-(void)setDelegate:(id)newDelegate {
delegate = newDelegate;
NSLog(#"delegate has been set in datePickerWindowController");
}
- (IBAction)selectDate:(NSDatePicker *)sender {
[delegate selectedDate:self.datePicker.dateValue];
[self.window close];
}
#end
#import <Cocoa/Cocoa.h>
#import "datePickerWindowController.h"
#interface AppDelegate : NSObject <NSApplicationDelegate, DatePickerProtocol, NSWindowDelegate>
#end
#import "AppDelegate.h"
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSDatePicker *timePicker;
#property (weak) IBOutlet NSTextField *textDate;
#property (retain) datePickerWindowController * myDatePickerWindowController;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
self.window.delegate = self;
[self.window setDelegate:self];
self.textDate.stringValue = [NSString stringWithFormat:#"%#",[NSDate date]];
datePickerWindowController * windowController = [[datePickerWindowController alloc] initWithWindowNibName:#"datePickerWindowController"];
self.myDatePickerWindowController = windowController;
self.myDatePickerWindowController.delegate = self;
[self.myDatePickerWindowController setDelegate:self];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
}
-(void)selectedDate:(NSDate *)date {
self.textDate.stringValue = [NSString stringWithFormat:#"%#", date];
}
- (IBAction)pickDateButton:(NSButton *)sender {
[self.window beginSheet:self.myDatePickerWindowController.window completionHandler:nil];
}
// Position sheet under text field
-(NSRect)window:(NSWindow *)window willPositionSheet:(NSWindow *)sheet usingRect:(NSRect)rect {
if (sheet == self.myDatePickerWindowController.window) {
NSRect r = self.textDate.frame;
r.origin.y = r.origin.y + 5;
return r;
} else {
return rect;
}
}
#end
I am assuming I have the delegate messed up somehow. Maybe in the xib or the code. I can not see why it works a second time though. Is this due to retain or how I am keeping the DatePicker around.
Many thanks for any help.
I have a UITextField set up in Interface Builder with a background image. The background shows up fine, but when I switch (in IB) the Class name to my UITextField subclass (ValidatedTextField), the background image doesn't show. Can anyone spot any reason why the image should not be there for my UITextView subclass?
Other info
Don't know if this helps but IB has also been giving me some trouble -- sometimes not allowing me to change the class name of these text fields..
//ValidatedTextField.h
#import <UIKit/UIKit.h>
#import "MBValidated.h"
#interface ValidatedTextField : UITextView <MBValidated>
// the maximum characters allowed
#property (assign, nonatomic) int mbMaxLength;
// an visual indicator of the validation state (checkmark, etc)
#property (strong, nonatomic) UIImageView *mbStatusImageView;
// whether the field can be empty
#property (assign, nonatomic) BOOL mbIsRequired;
// whether we have succesfully validated
#property (assign, nonatomic) BOOL mbIsValid;
// validate and update stored validated state
-(BOOL)mbValidate;
#end
// ValidatedTextField.m
#import "ValidatedTextField.h"
#implementation ValidatedTextField
#synthesize mbMaxLength, mbStatusImageView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
// set any default values here
-(void)mbSetDefaults
{
self.mbIsRequired = YES;
self.mbIsValid = YES;
}
-(BOOL)mbValidate
{
// validate length
if(self.text.length > self.mbMaxLength) self.mbIsValid = NO;
// validate empty or filled
if(self.text.length == 0 && self.mbIsRequired == YES) self.mbIsValid = NO;
return self.mbIsValid;
}
- (void)awakeFromNib
{
// set defaults
[self mbSetDefaults];
}
#end
// MBValidated Protocol
#import <Foundation/Foundation.h>
#protocol MBValidated <NSObject>
// whether the field can be empty
#property (assign, nonatomic) BOOL mbIsRequired;
// whether we have succesfully validated
#property (assign, nonatomic) BOOL mbIsValid;
// validate the item
-(BOOL)mbValidate;
#end
You should subclass UITextField and not UITextView.
I'm trying to write my first iPad app using Xcode 4: it's based on "Tabbed Application" template with two views. On the first view, user selects a City (label displays selection) and on the second wiew there is a IBOutletCollection(UITextField) NSArray *playersData. I want to set the city selected on first view as a default city on second view. I have checked storyboard connections and they seem to be ok.
I get nothing. Any idea?
First view :
#import
#interface pruebaF1FirstViewController : UIViewController
- (IBAction)selectCity:(UIButton *)sender;
#property (weak, nonatomic) IBOutlet UILabel *citySelected;
#end
First view implementation :
#import "pruebaF1FirstViewController.h"
#import "pruebaF1SecondViewController.h"
#interface pruebaF1FirstViewController ()
#property pruebaF1SecondViewController *secondView;
#end
#implementation pruebaF1FirstViewController
#synthesize citySelected;
#synthesize secondView;
- (void)viewDidLoad
...
- (IBAction)selectCity:(UIButton *)sender {
NSString *currentCity =[sender currentTitle];
citySelected.text=#"";
citySelected.text =[citySelected.text stringByAppendingString:currentCity];
/*writes null*/
secondView.defaultCity.text=[NSString stringWithFormat:#"%#" ,currentCity];
NSLog(#"%#",secondView.defaultCity.text);
}
#end
Second view header
#import <UIKit/UIKit.h>
#interface pruebaF1SecondViewController : UIViewController <UITextFieldDelegate>
#property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *playersData;
#property (retain, nonatomic) IBOutlet UITextField *defaultCity;
- (IBAction)eraseData:(UIButton *)sender;
- (IBAction)savePlayersData:(UIButton *)sender;
- (IBAction)termsPopUp:(UIButton *)sender;
/*- (void)writeDefaultCity:(NSString *)currentCity;*/
#end
Second view implementation
#import "pruebaF1SecondViewController.h"
#import "pruebaF1FirstViewController.h"
#interface pruebaF1SecondViewController ()
#property (nonatomic, strong)pruebaF1FirstViewController *prueba;
#end
#implementation pruebaF1SecondViewController
#synthesize playersData;
#synthesize defaultCity;
#synthesize prueba;
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[self setPlayersData:nil];
[self setDefaultCity:nil];
[super viewDidUnload];
}
/* Return or Done button dismiss keyboard*/
-(BOOL)textFieldShouldReturn:(UITextField *)boxes
{
for (UITextField *boxes in playersData) {
[boxes resignFirstResponder];
}
return YES;
}
....
/*Trying to get city's name from first view in two ways*/
/*-(void)setDefaultCity
{
NSString *defecto=prueba.citySelected.text;
self.defaultCity.text = defecto;
}*/
- (IBAction)eraseData:(UIButton *)sender {
for (UITextField *boxes in playersData) {
boxes.text = #" ";
}
}
/*
-(void)writeDefaultCity:(NSString *)currentCity
{
defaultCity.text =[NSString stringWithFormat:#"%#" ,currentCity];
NSLog(#"Ciudad elegida: %#",currentCity);
}*/
....
#end
Views are generally not loaded until they are displayed on the device, and may be unloaded at any time when not visible to save memory. This means that when you're setting the 'text' property of the 'defaultCity', that label has not yet been created.
Instead you should add a NSString * property to pruebaF1SecondViewController and set the value of the defaultCity label in viewDidLoad:
In the header:
#property (nonatomic, weak) UILabel *defaultCityLabel;
#property (nonatomic, strong) NSString *defaultCity;
In the implementation
- (void)viewDidLoad
{
[super viewDidLoad];
defaultCityLabel.text = defaultCity;
}
And in the first view controller, assign the string value instead of the label value.
I don't understand why I'm getting a EXC BAD ACCESS error when I call the mineHit method in my .m file. I understand that it's indicating that the button array has been released, but I don't understand why It would have been released at all.
#import "basicsViewController.h"
#implementation basicsViewController
#synthesize resetGame;
#synthesize scoreLabel;
#synthesize timeLabel;
#synthesize time;
#synthesize score;
-(void)newGame{
int index=0;
int yAxis=70;
for(int y=0;y<100;y=y+10){
int xAxis=20;
for( int x = 1; x < 11; x++) {
buttonArray[index] = [[UIButton alloc]init];
buttonArray[index] = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[buttonArray[index] setTag:index];
[buttonArray[index] addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
buttonArray[index].frame = CGRectMake(xAxis, yAxis, 26.0, 26.0);
NSLog(#"tag:%d xAxis:%d yAxis:%d",buttonArray[index].tag,(int)buttonArray[index].frame.origin.x,(int)buttonArray[index].frame.origin.y);
[self.view addSubview:buttonArray[index]];
xAxis=xAxis+28;
index=x+y;
}
yAxis=yAxis+28;
}
//generate bombs
for (int bombs=0;bombs<10;bombs++){
bombArray[bombs]= (arc4random()%99);
//TODO compare against bombArray to make sure of no duplicates
NSLog(#"BOMB AT %d",bombArray[bombs]);
}
}
- (IBAction)resetPress:(id)sender {
[self newGame];
}
- (void)buttonClicked:(UIButton*)button
{
BOOL hit;
NSLog(#"SELECTED BUTTON:%d",button.tag);
for (int b=0;b<10;b++){
if (button.tag==bombArray[b]){
//BOMB HIT
hit=YES;
b=10;
}
else {
//no bomb
hit=NO;
}
}
if (hit==YES){
//if hit
NSLog(#"HIT AT %d",button.tag);
[self mineHit];
}
else {
//if not hit
NSLog(#"%d is clean",button.tag);
[self cleanHit:button];
}
}
-(void)mineHit{
for (int d=0;d<100;d++){
NSLog(#"%i",buttonArray[d].tag);
buttonArray[d].enabled=NO;
[buttonArray[d] setTitle:#"*" forState:UIControlStateDisabled];
}
}
-(void)cleanHit:(UIButton*)button{
button.enabled=NO;
[button setTitle:#"!" forState:UIControlStateDisabled];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self newGame];
}
- (void)viewDidUnload
{
[self setResetGame:nil];
[self setScoreLabel:nil];
[self setTimeLabel:nil];
[super viewDidUnload];
}
#end
Here is my .h file
#import <UIKit/UIKit.h>
NSInteger bombArray[];
UIButton *buttonArray[];
#interface basicsViewController : UIViewController
#property (weak, nonatomic) IBOutlet UIButton *resetGame;
#property (weak, nonatomic) IBOutlet UILabel *scoreLabel;
#property (weak, nonatomic) IBOutlet UILabel *timeLabel;
#property int time;
#property int score;
-(void)newGame;
-(void)buttonClicked:(UIButton*)button;
-(void)mineHit;
-(void)cleanHit:(UIButton*)button;
#end
When I compile your code I get four warnings. All four warnings are the same and say:
Tentative array definition assumed to have one element
The warnings apply to the definition of your bombArray and buttonArray arrays in the interface (.h) file.
If we give the two arrays a size your -mineHit method works fine.
Change the beginning of your .h file to:
#import <UIKit/UIKit.h>
NSInteger bombArray[10];
UIButton *buttonArray[100];
#interface basicsViewController : UIViewController
The compiler generates warnings for a reason and it is a good idea to try and get your code to compile cleanly with no warnings or errors.
Update: While we are here there is no reason why you couldn't move these arrays inside the interface and declare them as instance variables. Doing this would mean that the arrays are associated with an individual instance of the view controller. It is unlikely that you'd have multiple instances of this view controller but it is better to do it correctly now than get bitten later.
#import <UIKit/UIKit.h>
#interface basicsViewController : UIViewController {
NSInteger bombArray[10];
UIButton *buttonArray[100];
}
Interestingly, moving the declaration into the interface turns the warnings into errors.
According to the View Controller Programming Guide, delegation is the preferred method to dismiss a modal view.
Following Apple's own Recipe example, i have implemented the following, but keep getting warnings that the addNameController:didAddName method is not found...
NameDelegate.h
#protocol NameDelegate
- (void)addNameController:(AddName *)addNameController didAddName:(NSString *)name;
#end
AddName.h
#interface AddName : UIViewController {
UITextField *nameField;
id delegate;
}
- (IBAction)doneAction;
- (id)delegate;
- (void)setDelegate:(id)newDelegate;
#property (nonatomic, retain) IBOutlet UITextField *nameField;
#end
AddName.m
- (IBAction)doneAction {
[delegate addNameController:self didAddName:[nameField text]];
}
- (id)delegate {
return delegate;
}
- (void)setDelegate:(id)newDelegate {
delegate = newDelegate;
}
ItemViewController.h
#import "NameDelegate.h"
#interface ItemViewController : UITableViewController <NameDelegate>{
}
#end
ItemViewController.m
- (void)addItem:(id)sender {
AddName *addName = [[AddName alloc] init];
addName.delegate = self;
[self presentModalViewController:addName animated:YES];
}
- (void)addNameController:(AddName *)addNameController didAddName:(NSString *)name {
//Do other checks before dismiss...
[self dismissModalViewControllerAnimated:YES];
}
I think all the required elements are there and in the right place?
Thanks
You haven't specified that the delegate property of AddName has to conform to the NameDelegate protocol.
Use this code in AddName.h:
#import "NameDelegate.h"
#interface AddName : UIViewController {
UITextField *nameField;
id <NameDelegate> delegate;
}
#property(nonatomic, retain) IBOutlet UITextField *nameField;
#property(nonatomic, assign) id <NameDelegate> delegate;
- (IBAction)doneAction;
#end