I'm writing a small project time management program for myself and have run into a problem which has confounded me.
They way it's set up is that I have an object called TCObject which I use in another object called ProjectTimerController. ProjectTimerController is a NSWindowController and it has it's own NIB file.
What I'm doing is pretty straight forward. When you click a line in a NSTableView ProjectTimerController finds a TCObject which corresponds to that line. It then loads info from that TCObject into an interface where you can view and edit some stuff.
Here's a screenshot of what it looks like:
Now when I change the text in NSTextView and then press the Add button the -saveString function is called and currentSelection (which is a TCObject and represents the currently selected line) and it's notes variable is set. I know that _notes is set as the new value as NSLog function logs the correct string being in _notes when setString is run. The same, correct, string is logged in -tableViewSelectionDidChange: just before currentSelection is set as the newly selected object.
But if I select the line where I just changed the notes it just loads the same text, "Initial String" and checking _notes tells me it's "Initial String".
Thing I don't have this problem with isFinished. When the Finished check box is toggled I set the corresponding TCObjects' isFinished Boolean value to the same value as the checkbox. This the object remembers and correctly changes depending on what line I have selected.
[EDIT]
*I've added a clearer explanation here.
I click a line in the NSTableView (lets say the top one)
This loads a corresponding TCObject from the myProjects array and that object's variable are added to the Notes NSTextView box and Finished is toggled on or off.
If I now write into The Notes box and press "Add" the text there is set into that TCObject's _notes variable.
So If I click another line some other text is loaded into the Notes box. Clicking back on the top line should give me the string I just wrote into Notes in step 3. But it doesn't. _notes always seems to contain the string I set when I initialize it in the -init method.
The "Finished" checkbox works fine. When I click that the state is saved and loaded correctly when I click a line.
I know that _notes is correctly set when I press the Add button as the NSLog method in setString logs the string I have written into Notes when I press the Add button.
[/EDIT]
Here below is a barebones version of TCObject and ProjectTimerController.
//TCObject.h
#interface TCObject : NSObject
{
NSString *_notes;
Boolean _isFinished;
}
#property (retain, nonatomic) NSString *notes;
#property (nonatomic) Boolean isFinished;
#end
//TCObject.m
#import "TCObject.h"
#implementation TCObject
#synthesize notes = _notes, isFinished = _isFinished;
-(id)init{
if (self = [super init]) {
self.notes = #"Initial string";
self.isFinished = NO;
}
return self;
}
- (void)dealloc {
[_notes release]; _notes = nil;
[super dealloc];
}
-(void)setNotes:(NSString *)notes {
[notes retain];
[_notes release];
_notes = notes;
NSLog(#"Setting _notes as: %#", _notes);
}
-(NSString *)notes {
NSLog(#"Getting _notes, which is: %#", _notes);
return _notes;
}
#end
//ProjectTimerController.m
- (id)initWithWindow:(NSWindow *)window {
self = [super initWithWindow:window];
if (self)
{
myProjects = [[NSMutableArray alloc]init];
currentSelection = nil;
TCObject *newProject = [[TCObject alloc] init];
TCObject *newProject2 = [[TCObject alloc] init];
TCObject *newProject3 = [[TCObject alloc] init];
TCObject *newProject4 = [[TCObject alloc] init];
[myProjects addObject:newProject];
[myProjects addObject:newProject2];
[myProjects addObject:newProject3];
[myProjects addObject:newProject4];
}
return self;
}
-(IBAction)isFinishedToggle:(id)sender {
if(currentSelection != nil){
currentSelection.isFinished = finishedCheckBtn.state;
}
}
-(IBAction)saveString:(id)sender {
if(currentSelection != nil){
currentSelection.notes = [[notesField textStorage] string];
}
}
//delegate function for NSTableView
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification {
NSInteger selectedIndex = [table selectedRow];
if(selectedIndex == -1){
return;
}
//here the correct notes string is printed
NSLog(#"curr: %i", currentSelection.notes);
currentSelection = [myProjects objectAtIndex:selectedIndex];
NSString *notesInfo = currentSelection.notes;
Boolean isFinishedInfo = currentSelection.isFinished;
[notesField setString:notesInfo];
[finishedCheckBtn setState:isFinishedInfo];
}
Finally found the problem. Seems that changing notes in this way:
-(IBAction)saveString:(id)sender {
if(currentSelection != nil){
currentSelection.notes = [[notesField textStorage] string];
}
}
causes some problems. Everything works fine if I do it this way:
-(IBAction)saveString:(id)sender{
if(currentSelection != nil){
NSString *temps = [NSString stringWithFormat:#"%#", [[notesField textStorage] string]];
currentSelection.notes = temps;
}
}
So I'm guessing what was going on is that _notes was pointing to the text contained in my NSTextView. So when I changed the text there _notes also changed or something like that...
Related
I am trying to have multiple players for a card matching game. I have made a class that is a child of NSObject called Player. A Player object will have a name, high score, last score, and boolean is currently playing instance. I am trying to create a new player every time a new name is entered into a text field. I will work on storing the players later. Right now I just wish to make a new Player, have a user enter their name in the text field. And store the user's response in the text field. The following is my Player.m file
#import "Player.h"
#implementation Player
-(NSString *) nameOfPlayer:(Player *)playerName{
return self.name;
}
-(void) setPlayerName:(NSString *) nameOf{
self.name = nameOf;
self.lastScore = 0;
self.highestScore = 0;
self.playing = YES;
return;
}
-(id)init{
self = [super init];
self.name = #"";
self.lastScore = 0;
self.highestScore = 0;
self.playing = YES;
return self;
}
#end
I didn't originally have that init until I tried looking for a solution online. I am a bit new to iOS coding so I wasn't sure how to set up a constructor (or if they even have those). Below is how I tried to instantiate a Player object:
- (IBAction)handleButtonClick:(UIButton *)sender {
Player * friend = nil;
nameText = self.nameEntryField.text;
[friend setPlayerName:nameText];
NSLog(#"%#", [friend nameOfPlayer:friend]);
}
My app breaks its thread as soon as I try to setPlayerName. I am a bit stuck on this so any help would be appreciated.
- (IBAction)handleButtonClick:(UIButton *)sender {
Player * friend = [[Player alloc] init]; // Initialize this
nameText = self.nameEntryField.text;
[friend setPlayerName:nameText];
NSLog(#"%#", [friend nameOfPlayer:friend]);
}
You have done like Player * friend = nil; mean this is the nil object. And then you are trying to use method setPlayerName:on nil object. Because of this app breaks.
So you need to initialize object using [[Player alloc] init].
This is embarassing. I was unfamiliar with the xCode IDE and accidentally had selected that line of code for a breakpoint. The code is fine after previous answer.
I have an application that, in part, loops through the contents of an NSSet and displays a UIAlertView for each item found in the set. When there is only one item in the set, the UIAlertView behaves itself properly. However, if there's more than one, the first view flashes up (normally with the contents of the last item in the set) and then disappears without any user intervention. The first item in the NSSet will then display and wait for a response, before displaying the next item in the NSSet and so on.
It is the same experience as is being described in this unresolved question: IPHONE: UIAlertView called twice in a custom function/IBAction
Here's the code:
#import "CalcViewController.h"
#interface CalcViewController()
#property (nonatomic) int variablesCount;
#property (nonatomic, strong) NSMutableDictionary *variablesSet;
#end
#implementation CalcViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.variablesSet = [[NSMutableDictionary alloc] init];
}
- (IBAction)variablePressed:(UIButton *)sender
{
[[self calcModel] setVariableAsOperand:sender.titleLabel.text];
self.expressionDisplay.text = [[self calcModel] descriptionOfExpression:self.calcModel.expression];
}
- (IBAction)solveExpressionPressed:(UIButton *)sender {
self.variablesCount = 0;
[self.variablesSet removeAllObjects];
NSSet *variablesCurrentlyInExpression = [[NSSet alloc] initWithSet:[CalcModel variablesInExpression:self.calcModel.expression]];
self.variablesCount = [variablesCurrentlyInExpression count];
if (variablesCurrentlyInExpression){
for (NSString *item in variablesCurrentlyInExpression) {
UIAlertView *alertDialog;
alertDialog = [[UIAlertView alloc] initWithTitle:#"Enter value for variable"
message:item
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
alertDialog.alertViewStyle=UIAlertViewStylePlainTextInput;
UITextField * alertTextField = [alertDialog textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
[alertDialog show];
}
}
}
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0){
if ([[alertView textFieldAtIndex:0] text]){
self.variablesSet[alertView.message] = [[alertView textFieldAtIndex:0] text];
}
}
if ([self.variablesSet count] == self.variablesCount){
NSLog(#"time to solve");
[[self calcDisplay] setText:[NSString stringWithFormat:#"%g", [CalcModel evaluateExpression:self.calcModel.expression usingVariableValues:self.variablesSet]]];
}
}
I've checked the IBActions behind the button that triggers the solveExpressionPressed method and that is the only one that exists. I've also placed some logging before the [alertDialog show]; line and it is only called twice when the variablesCurrentlyInExpression NSSet contains two values, yet the UIAlertView appears three times (flashing up once).
Finally, i've tried it without the following code:
UITextField * alertTextField = [alertDialog textFieldAtIndex:0];
alertTextField.keyboardType = UIKeyboardTypeNumbersAndPunctuation;
and the problem still occurs, so I don't think it's that.
I've been stuck on this a while and haven't figured it out (hence the post!!), so any help would be greatly appreciated.
Thanks
Try showing the first UIAlertView and then showing the second after the first is dismissed.
What's happens is if an app or the OS calls [alert show] and a UIAlertView is already being displayed, the original alertView gets placed in a queue and the new one is presented. When the new one is dismissed, the original UIAlertView is re-shown.
Hope this helps
Easily fixed with a boolean flag that you set to YES when the first alert is shown. Then when the second match is found and the boolean is already YES because the alert is visible you won't show it. Then again you might want to know the exact amount of matches in the NSSet. In that case you keep track with a counter and show the alert after the match function has been done and the counter is not 0.
Avoid showing the alert inside the method of the button trigger. Instead split every function into different sets of methods. Not just for making your function work but maintainability of the code later.
To get this done, you'll need to keep some extra state in your class, like this...
#property (strong, nonatomic) NSMutableSet *promptVariables;
#property (strong, nonatomic) NSString *promptVariable;
#property (strong, nonatomic) NSMutableDictionary *promptResults;
You can probably get away with less by keeping some in your model as it is (or hiding a little in the alert view message as you cleverly do currently), but I'll use all new variables for clarity.
When you want to make several prompts, set up your state like this...
self.promptVariables = [[NSSet alloc] initWithSet:[CalcModel variablesInExpression:self.calcModel.expression]];
[self promptForVariables];
Define promptForVariables to bail if it has no work to do (promptVariables is empty) or remove one and do the alert for it.
- (void)promptForVariables {
if (![self.promptVariables count]) return;
self.promptResults = [NSMutableDictionary dictionary];
self.promptVariable = [self.promptVariables anyObject];
[self.promptVariables removeObject:self.promptVariable];
// do your alert here, I won't repeat your code
}
Then when the alert is done, process the result as you have and call promptForVariables again. The next time, since you've changed state, it has less work to do.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
if (buttonIndex == 0){
if ([[alertView textFieldAtIndex:0] text]){
[self.promptResults setValue:[[alertView textFieldAtIndex:0] text] forKey:self.promptVariable];
}
[self performSelector:#selector(promptForVariables) withObject:nil afterDelay:0.0];
}
}
When this is done, promptResults will contain variable names as keys and user inputs as values.
According to the official FAQ from ver.2 to customize your text/content depending on what sharer was selected by the user, you need:
subclass from SHKActionSheet and override
dismissWithClickedButtonIndex
set your new subclass name in
configurator (return it in (Class)SHKActionSheetSubclass;).
It doesn't work for me. But even more: I put
NSLog(#"%#", NSStringFromSelector(_cmd));
in (Class)SHKActionSheetSubclass to see if it's even got called. And it's NOT ;(( So ShareKit doesn't care about this config option...
Has anybody worked with this before?
thank you!
UPD1: I put some code here.
Here's how my subclass ITPShareKitActionSheet looks like. According to the docs I need to override dismissWithClickedButtonIndex:animated:, but to track if my class gets called I also override the actionSheetForItem::
+ (ITPShareKitActionSheet *)actionSheetForItem:(SHKItem *)item
{
NSLog(#"%#", NSStringFromSelector(_cmd));
ITPShareKitActionSheet *as = (ITPShareKitActionSheet *)[super actionSheetForItem:item];
return as;
}
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animate
{
NSLog(#"%#", NSStringFromSelector(_cmd));
NSString *sharersName = [self buttonTitleAtIndex:buttonIndex];
[self changeItemForService:sharersName];
[super dismissWithClickedButtonIndex:buttonIndex animated:animate];
}
And here's what I do in code to create an action sheet when user presses 'Share' button:
- (IBAction)shareButtonPressed:(id)sender
{
// Create the item to share
SHKItem *item = [SHKItem text:#"test share text"];
// Get the ShareKit action sheet
ITPShareKitActionSheet *actionSheet = [ITPShareKitActionSheet actionSheetForItem:item];
// Display the action sheet
[actionSheet showInView:self.view]; // showFromToolbar:self.navigationController.toolbar];
}
When I run this code, press 'Share' button and select any sharer I expect to get two lines in log:
actionSheetForItem: - custom action sheet got created
dismissWithClickedButtonIndex:animated: - custom mechanics to
process action sheet's pressed button got called.
But for some reason I get only the first line logged.
I was having the same issues but I've suddenly got it to call my Subclass successfully.
Firstly My Configurator is setup as so:
-(Class) SHKActionSheetSubclass{
return NSClassFromString(#"TBRSHKActionSheet");
}
Now My Subclass:
.h File
#import "SHKActionSheet.h"
#interface TBRSHKActionSheet : SHKActionSheet
#end
.m implementation override:
#import "TBRSHKActionSheet.h"
#import "SHKActionSheet.h"
#import "SHKShareMenu.h"
#import "SHK.h"
#import "SHKConfiguration.h"
#import "SHKSharer.h"
#import "SHKShareItemDelegate.h"
#implementation TBRSHKActionSheet
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
+ (SHKActionSheet *)actionSheetForItem:(SHKItem *)i
{
NSLog(#"%#", NSStringFromSelector(_cmd));
SHKActionSheet *as = [self actionSheetForType:i.shareType];
as.item = i;
return as;
}
- (void)dismissWithClickedButtonIndex:(NSInteger)buttonIndex animated:(BOOL)animated
{
NSInteger numberOfSharers = (NSInteger) [sharers count];
// Sharers
if (buttonIndex >= 0 && buttonIndex < numberOfSharers)
{
bool doShare = YES;
SHKSharer* sharer = [[NSClassFromString([sharers objectAtIndex:buttonIndex]) alloc] init];
[sharer loadItem:item];
if (shareDelegate != nil && [shareDelegate respondsToSelector:#selector(aboutToShareItem:withSharer:)])
{
doShare = [shareDelegate aboutToShareItem:item withSharer:sharer];
}
if(doShare)
[sharer share];
}
// More
else if ([SHKCONFIG(showActionSheetMoreButton) boolValue] && buttonIndex == numberOfSharers)
{
SHKShareMenu *shareMenu = [[SHKCONFIG(SHKShareMenuSubclass) alloc] initWithStyle:UITableViewStyleGrouped];
shareMenu.shareDelegate = shareDelegate;
shareMenu.item = item;
[[SHK currentHelper] showViewController:shareMenu];
}
[super dismissWithClickedButtonIndex:buttonIndex animated:animated];
}
Finally on my implementation file I've not modified the call to SHKActionSheet as Vilem has suggested because of some dependancies that seemed to cause conflicts for me.
So this is my caller (straight from tutorial):
NSURL *url = [NSURL URLWithString:#"http://getsharekit.com"];
SHKItem *item = [SHKItem URL:url title:#"ShareKit is Awesome!" contentType:SHKURLContentTypeWebpage];
// Get the ShareKit action sheet
SHKActionSheet *actionSheet = [SHKActionSheet actionSheetForItem:item];
// ShareKit detects top view controller (the one intended to present ShareKit UI) automatically,
// but sometimes it may not find one. To be safe, set it explicitly
[SHK setRootViewController:self];
// Display the action sheet
[actionSheet showFromToolbar:self.navigationController.toolbar];
This Calls no problems for me.
edit: by far the best way to achieve this is to use SHKShareItemDelegate. More info is in ShareKit's FAQ.
So I have the following code:
- (void)addSupportLinksMenuItems
{
NSString *subMenuTitle;
NSString *getURL;
if (!supportLinks) {
supportLinks = [[NSArray alloc] initWithArray:[settings objectForKey:#"supportLinks"]];
}
for(NSDictionary *object in supportLinks){
// A couple of Keys in the Dict inside the Array
subMenuTitle = [object objectForKey:#"subMenuTitle"];
getURL = [object objectForKey:#"getURL"];
NSInteger n = [ supportLinks indexOfObject:object];
NSInteger menuTag = n +255;
//[ supportLinkItem setImag
supportLinkArrayItem = [supportLinkItem
insertItemWithTitle:subMenuTitle
action:#selector(openSupportLink:)
keyEquivalent:#""
atIndex:n];
// Set a menu tag to programatically update in the future
[ supportLinkArrayItem setTag:menuTag];
[ supportLinkArrayItem setToolTip:getURL];
[ supportLinkArrayItem setTarget:self];
}
//supportLinkItem
}
This dynamically generates an submenu items from an NSArray and allows me to open the url based on the choice that was selected (in a specific browser):
-(IBAction)openSupportLink:(id)sender
{
NSLog(#"Was passed Menu: %#",sender);
NSInteger menuTag = [sender tag];
NSInteger n = menuTag - 255;
NSString *getURL = [[supportLinks objectAtIndex:n] objectForKey:#"getURL"];
[self openPageInSafari:getURL];
}
- (void)openPageInSafari:(NSString *)url
{
NSDictionary* errorDict;
NSAppleEventDescriptor* returnDescriptor = NULL;
NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:
[NSString stringWithFormat:
#"\
tell app \"Safari\"\n\
activate \n\
make new document at end of documents\n\
set URL of document 1 to \"%#\"\n\
end tell\n\
",url]];
returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
[scriptObject release];
}
My question is, while this seems to work great , I would like to set an image for the NSMenu supportLinkItem, here is what my .h file looks like:
IBOutlet NSMenu *supportLinkItem;
NSMenuItem *supportLinkArrayItem;
And the outlet is linked to the sub menu item, as I have created its (parent? -terminology?) as a NSmenu, it does not allow me to access this as the - (void)setImage:(NSImage *)menuImage method as its not a NSMenuitem. Now I think maybe I have just done something weird here as , technically when you drag the "Sub Menu Item" into interface builder its a NSMenuItem not a NSMenu, again my code works flawlessly except for my inability to set the image of the menu, Which I think is a no go but perhaps there is similar way to read from an NSArray to populate a set of sub menus.
I was able to resolve this by updating the image in the nib file as the nib thinks its a nsmenuitem.
Need to pass a button id from one .m to another and clear my table view at the same time so i can load new data in. Nothing seems to be happening, the table still contains what it was populated with on viewDidLoad and the background image does not change. Please help.
There are no warning or errors so im not sure what im doing wrong but the value of senderIdentifier in ClassTwo.h is always 0
Thank you.
in my classOne.m i have the following button action
-(IBAction) btnPushViewtabel:(id)sender{
ClassTwo *classtwo = [[ClassTwo alloc]init];
classtwo.myTable = nil;
classtwo.senderIdentifier = [sender tag];
[self.view addSubview:viewtableController.view];
}
in ClassTwo.h
#interface ClassTwo : UIViewController <UITableViewDelegate, UITableViewDataSource> {
....
int senderIdentifier;
}
#property (nonatomic, readwrite) int senderIdentifier;
in ClassTwo.m
if(senderIdentifier == 0){
// getNewData
//set background img1
} else {
// getNewData
//set background img2
}
if(mySenderIdentifier == 0){ //
getNewData //set background img1 }
else { // getNewData //set
background img2 }
what method is this code block in?
Edit:
This Line:
classtwo.mySenderIdentifier = [sender
tag];
is called after:
viewDidLoad
In other words- when you are checking it you havn't actually set it yet.
There are a number of ways to solve this but my personal preference would be to create a custom init method for classTwo like this:
-(id)initWithSenderIdentifier:(int)identifier{
if(self=[super init]){
self.senderIdentifier = identifier;
self.myTable = nil;
}
return self;
}
Or elimate the need to store the int (given that's only used to set the background once)
-(id)initWithSenderIdentifier:(int)identifier{
if(self=[super init]){
if(identifier)//set backgroundimg1;
else //set backgroundimg2;
self.myTable = nil;
}
return self;
}
you use it like this:
ClassTwo *classtwo = [[ClassTwo alloc]
initWithSenderIdentifier:[sender tag]];
If this is helpful, please mark it was answered