Edit individual chars in Objective-C - objective-c

I'm learning about Cocoa/Objective-C, and I'm trying to be able to modify a NSString and edit individual chars in the string. I've managed to be able to change it to an array of chars, and change it back, but when I try to do c[0] = 'b', it says, "read-only variable is not assignable". I've included the .m file and .h file.
This is the .m file:
import "AppDelegate.h"
#implementation AppDelegate
#synthesize textField, myLabel;
-(IBAction)changeLabel:(id)sender {
NSString *message = [[NSString alloc] initWithFormat:#"Hello, %#!", [textField stringValue]];
NSString *message2 = [[NSString alloc] initWithFormat:#"%# This part was added on afterwards.", message];
const char *c = [message2 UTF8String];
c[0] = 'b';
NSString *output;
output = [NSString stringWithCString:c encoding:NSASCIIStringEncoding];
[myLabel setStringValue:output];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
}
#end
This is the .h file:
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet NSTextField *textField;
#property (weak) IBOutlet NSTextField *myLabel;
-(IBAction)changeLabel:(id)sender;
#property (weak) IBOutlet NSTextField *changeButtonText;
#end
I'm assuming the .h file isn't relevant, but I though it'd include it.

NSString is a container for an immutable string. Taken from official reference:
The NSString class declares the programmatic interface for an object that manages immutable strings. An immutable string is a text string that is defined when it is created and subsequently cannot be changed.
Basically, by using just cocoa, you can generate new NSStrings by replacing characters in existing strings, for example with:
- (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement
but you can't modify the same object. Exactly for this reason UTF8String returns a const char* which is an immutable pointer to a sequence of character. That's why you get the error.
You can use the provided NSString methods to generate new strings or copy the const char* C string to a char* (through, for example, strdup).

c is a const char* -> its initialising value is its value throughout the whole runtime. Maybe you should erase the const keyword.

In Objective-C an NSString instance is not modifiable, for a modifiable string you need to NSMutableString. Alternatively you can create a new string from an old one. Unfortunately NSMutableString/NSString does not have the inverse of characterAtIndex:, to change a single character you need to use stringByReplaceCharactersInRange:withString:/replaceCharactersInRange:withString: where you supply a range - location & length - of the characters to change. Here is part of your code re-written to use these, first creating a new string:
NSString *message2 = [[NSString alloc] initWithFormat:#"%# This part was added on afterwards.", message];
NSString *output = [message2 stringByReplacingCharactersInRange:NSMakeRange(0,1) withString:#"b"];
This first option creates a new string. Using a modifiable string:
NSMutableString *message2 = [[NSMutableString alloc] initWithFormat:#"%# This part was added on afterwards.", message];
[message2 replaceCharactersInRange:NSMakeRange(0,1)
This second option changes the string referenced by message2.
Whether you create new strings or use a modifiable string will depend on how many changes you need to make and whether you need to keep the original string.

Related

How do you set the text of a label to an array value?

I would like to set a value of my array to a label.
Array declaration:
//
// ViewController.h
// Cornell Notes
//
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController{
NSString *details[8][8];
NSString *subtitles[8];
}
I am not allowed to do:
self.label.text = subtitles[0];
How can I do this?
This:
#interface ViewController : UIViewController{
NSString *details[8][8];
NSString *subtitles[8];
}
Should Be:
#interface ViewController : UIViewController
#property (strong, nonatomic) NSMutableArray *details;
#property (strong, nonatomic) NSMutableArray *subtitles;
You could declare it similar to the way you had it, but I believe this is the preferred current syntax. Others may correct me on this. Most important is that we declare NSMutableArray's as opposed to NSString's. You are doing C-style declarations, which will be a bit different with these. I chose NSMutableArray as opposed to NSArray because it looks like you want to be able to add objects at runtime.
And this:
self.label.text = subtitles[0];
should be:
if (!_subtitles) _subtitles = [NSMutableArray new];
[_subtitles insertObject:self.label.text atIndex:0];
This line:
if (!_subtitles) _subtitles = [NSMutableArray new];
Is just to make sure that our _subtitles dictionary exists, and if it does, we make sure that it isn't overwritten. the [NSMutableArray new] syntax is something that I personally like because it looks clean; however, many prefer [[NSMutableArray alloc]init]. Just so you're aware.

Binding NSTextField to NSString

I have a very simple question I hope someone can answer as I start understanding bindings. I want to programmatically change my NSString value and have the NSTextField update to that value thru bindings. I have a NSTextField and NSLabel. To represent the value of the myString properly changing I have a NSButton.
I have NSTextField's Value bound to myString property of App Delegate with Continuously Update Value checked.
I have NSLabel's Value bound to myString Property of App Delegate.
I have NSButton outlet hooked to setDefault method.
When I type in NSTextField the NSLabel updates as expected but when I click the button the myString property is updated but not in the NSTextField.
What do I need to do to get the NSTextField update to the myString property????
AppDelegate.h
#interface AppDelegate : NSObject<NSApplicationDelegate>
{
NSString *myString;
}
#property (assign) IBOutlet NSWindow *window;
#property NSString *myString;
- (IBAction)setDefault:(id)sender;
#end
AppDelegate.m
#implementation AppDelegate
#synthesize window = _window;
#synthesize myString;
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
myString = #"This is a string";
}
- (IBAction)setDefault:(id)sender
{
NSLog(#"%#", myString);
myString = #"This is a string";
NSLog(#"%#", myString);
}
#end
It shouldn't be
myString = #"This is a string";
but this:
self.myString = #"This is a string";
both in -applicationDidFinishLaunching: and in -setDefault:. Don't forget to specify self in your NSLog statements as well. You'd probably like to specify a different string in -setDefault: so that you can actually see that a change is taking place.
One other thing: You're effectively saying that you want to assign to myString, but that's not appropriate for an object. Instead of:
#property NSString *myString;
you should instead use
#property (copy) NSString *myString;
or at least
#property (retain) NSString *myString;
The former is preferred because passing a NSMutableString instance effectively copies it as a NSString, while passing a NSString instance simply retains it.
Good luck to you in your endeavors.
I recommend that you prefix your member variables. This way you can distinguish between setting the member directly or using the setter. In your example I would do the following.
#interface AppDelegate : NSObject<NSApplicationDelegate>
{
NSString *m_myString;
}
#property (assign) IBOutlet NSWindow *window;
#property NSString *myString;
- (IBAction)setDefault:(id)sender;
#end
...
#implementation AppDelegate
#synthesize window = _window;
#synthesize myString = m_myString;
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
self.myString = #"This is a string";
}
- (IBAction)setDefault:(id)sender
{
NSLog(#"%#", m_myString);
self.myString = #"This is a string";
NSLog(#"%#", m_myString);
}
#end
Notice, that I changed the #synthesize to assign the member variable.
To clarify:
self.myString = #"This is a string";
.. is an alternative syntax for ...
[self setMyString:#"This is a string"];
You can also set the member directly ...
[self willChangeValueForKey:#"myString"];
m_myString = #"This is a string";
[self didChangeValueForKey:#"myString"];
But then you need to "inform" observers of the binding as illustrated above.

Strange behavior with NSString instance variable

Here is part of a class definition in an iOS program using reference counting (ARC):
#interface CapViewController : UIViewController
{
NSString *bottomBn;
NSString *topBn;
}
#property (nonatomic, strong) NSString *bottomBn;
#property (nonatomic, strong) NSString *topBn;
#end
In the implementation I synthesize them:
#implementation CapViewController
#synthesize bottomBn;
#synthesize topBn;
The problem is when I try to assign values. If I step through the following lines in a class method (first time each instance variable is being used):
bottomBn = [NSString stringWithString:#"bottomBn"];
topBn = [NSString stringWithString:#"topBn"];
After the first line has executed, the value of topBn becomes #"bottomBn" and bottomBn is nil
The second line has no impact.
If I change the order the instance variables are defined in the class, i.e.:
NSString *topBn;
NSString *bottomBn;
then the first assignment has no effect and the second assignment results in "topBn" being assigned to bottomBn.
Using local variables it works as expected:
NSString *localbottomBn = [NSString stringWithString:#"defaultbottombutton"];
NSString *localtopBn = [NSString stringWithString:#"defaulttopbutton"];
This seems bizarre behavior to me. I'd appreciate any help.
You are not setting the autoreleased strings, you should set the strings as:
self.bottomBn = [NSString stringWithString:#"bottomBn"];
self.topBn = [NSString stringWithString:#"topBn"];
I have same problems with other types and objects (even CGFloat and CGPoint). I think the problem is in debugger. Try to print you strings instead of look variables via debugger. For me NSLog function print what I expected.
I have no ideas why debugger have this unpredictable behavior (may be it's a bug), but now I prefere "NSLog debugging". It is sad.

Problem with parsing strings

I am trying to put a line of dialog on each of a series of images.
To match the dialog line with the correct image, I end each line with a forward slash (/) followed by a number to identify the matching image. I then parse each line to get the dialog and then the reference number for the image.
It all works fine except that when I put the dialog line into a textView I get the whole line in the textView instead of the dialog part.
What is confusing is that the console seems to indicate that the parsing of the dialog line has been carried out correctly.
Here are the details of my coding:
#interface DialogSequence_1ViewController : UIViewController {
IBOutlet UIImageView *theImage;
IBOutlet UITextView *fullDialog;
IBOutlet UITextView *selectedDialog;
IBOutlet UIButton *test_1;
IBOutlet UIButton *test_2;
IBOutlet UIButton *test_3;
NSArray *arrayLines;
IBOutlet UISlider *readingSpeed;
NSArray *cartoonViews;
NSMutableString *dialog;
NSMutableArray *dialogLineSections;
int lNum;
}
#property (retain,nonatomic) UITextView *fullDialog;
#property (retain,nonatomic) UITextView *selectedDialog;
#property (retain,nonatomic) UIButton *test_1;
#property (retain,nonatomic) UIButton *test_2;
#property (retain,nonatomic) UIButton *test_3;
#property (retain,nonatomic) NSArray *arrayLines;
#property (retain,nonatomic) NSMutableString *dialog;
#property (retain,nonatomic) NSMutableArray *dialogLineSections;
#property (retain,nonatomic) UIImageView *theImage;
#property (retain,nonatomic) UISlider *readingSpeed;
-(IBAction)start:(id)sender;
-(IBAction)counter:(id)sender;
-(IBAction)runNextLine:(id)sender;
#end
#implementation DialogSequence_1ViewController
#synthesize fullDialog;
#synthesize selectedDialog;
#synthesize test_1;
#synthesize test_2;
#synthesize test_3;
#synthesize arrayLines;
#synthesize dialog;
#synthesize theImage;
#synthesize readingSpeed;
#synthesize dialogLineSections;
-(IBAction)runNextLine:(id)sender{
//Get dialog line to display from the arrayLines array
NSMutableString *dialogLineDetails;
dialogLineDetails =[arrayLines objectAtIndex:lNum];
NSLog(#"dialogLineDetails = %#",dialogLineDetails);
//Parse the dialog line
dialogLineSections = [dialogLineDetails componentsSeparatedByString: #"/"];
selectedDialog.text =[dialogLineSections objectAtIndex: 0];
NSLog(#"Dialog part of line = %#",[dialogLineSections objectAtIndex: 0]);
NSMutableString *imageBit;
imageBit = [dialogLineSections objectAtIndex: 1];
NSLog(#"Image code = %#",imageBit);
//Select right image
int im = [imageBit intValue];
NSLog(#"imageChoiceInteger = %i",im);
//------more code
}
I get a warning on the line:
dialogLineSections = [dialogLineDetails componentsSeparatedByString: #"/"];
warning: incompatible Objective-C types assigning 'struct NSArray *', expected 'struct NSMutableArray *'
I don't quite understand this and have tried to change the types but to no avail.
Would be grateful for some advice here.
The warning tells you exactly what the problem is. -componentsSeparatedByString: returns an immutable instance of NSArray, but you're assigning that result to a variable of type NSMutableArray. So you need to either change the variable to NSArray (in which case you can't modify it) or make a mutable copy of the components array (via -mutableCopy, which you must balance with -release or -autorelease to avoid memory leaks.)
The forward slash character is an escape character so you shouldn't use it as a delimiter. This can lead to random errors in string processing. Pick something else, preferably an arbitrary string like !123!
You are getting the warning because componentsSeparatedByString: returns a NSArray instead of an NSMutableArray and you are assigning the static array to a mutable array pointer. Instead use:
self.dialogSections=[NSMutableArray arrayWithArray:[dialogLineDetails componentsSeparatedByString: #"/"]];

Objective-C NSArray

I'm new to Obj-C and iPhone SDK. The test application I'm stock with is a color switcher containing two buttons ("Back", "Forward") and one text label. The idea is to switch between rainbow colors (background) and setting an appropriate text label in a cyclic manner.
I declared NSArray (which is to contain colors names) in RainbowViewController.h, synthesized it in RainbowViewController.h and I can't add any string into that array.
This is "h" file:
#import <UIKit/UIKit.h>
#interface RainbowViewController : UIViewController {
IBOutlet UILabel *currentColorTextLabel;
NSArray *colorsArray;
NSString *msg;
}
#property (nonatomic, retain) IBOutlet UILabel *currentColorTextLabel;
#property (nonatomic, retain) NSArray *colorsArray;
#property (nonatomic, retain) NSString *msg;
- (IBAction) pressForwardButton;
- (IBAction) pressBackButton;
#end
This is "m" file:
#import "RainbowViewController.h"
#import <Foundation/Foundation.h>
#implementation RainbowViewController
#synthesize currentColorTextLabel;
#synthesize colorsArray;
#synthesize msg;
int currentArrayIndex = 0;
colorsArray = [[NSArray alloc] init]; //here i get "Initializer element is not constant" error message
[coloursArray addObject:#"Red"]; //here I get "Expected identifier or '(' before '[' token"
[coloursArray addObject:#"Orange"];
//etc
- (IBAction) pressForwardButton {
//here I'm going to increment currentArrayIndex, set an appropriate color, and update a currentColorTextLabel based on currentArrayIndex.
}
- (IBAction) pressBackButton {
}
//auto-genereted code here
#end
I'm new to obj-c as well, but I think you need to initialize the array with objects, or use an NSMutableArray if you want to add objects after it is created.
You have the code that should go in your init method just sitting out in the middle of the file. You can't set instance variables like that.
jasongetsdown is correct. You need to instantiate the NSArray object with the objects it will contain and nil terminated.
#"Red", #"Blue", nil
If you wish to have an array that you can change you need to make it a Mutable Array.
However, you have another problem here. Your property that you are synthesizing and allocating for is an object named colorsArray and you are trying to pass a method to a coloursArray object, two different spellings.