I have a function on a class "loadingViewController" that needs to be accessed from other classes. First time that I call function like follows, it works but if I then call it again from another class do not because allocates it again and reset parameters. Same if I create an instance method. How to simply call a function from another class without init or allocate again? Probably basic newbie issue... Thanks.
class was declared in header and properly synthesized.
self.loadingController = [[loadingViewController alloc] initWithNibName:#"loadingViewController" bundle:nil];
[loadingController incrementProgress:0.1];
Hard to say for sure without seeing more code, but I'm thinking you just need to make sure you only initialize the loadingController once:
if ( self.loadingController == nil ) {
self.loadingController = [[loadingViewController alloc] initWithNibName:#"loadingViewController" bundle:nil];
}
[self.loadingController incrementProgress:0.1];
You can implement protocols here. Protocols are used to call methods of another class from one class. In general it will define the set of methods which your class will implement. TO see how to implement it you can see this answer.
I would do this:
-(void) loadingViewController
{
if ( self.loadingController == nil ) {
self.loadingController = [[loadingViewController alloc] initWithNibName:#"loadingViewController" bundle:nil];
}
[self.loadingController incrementProgress:0.1];
}
AND make sure you don't call [xyz loadingViewController] from any other thread than the main UI thread.
It looks like the reason you want to call a function on a view controller is to present the progress of a long operation to the user.
The more common approach is to have the view controller start the operation and then observe it's progress, updating the view accordingly.
Related
I am designing a new application by modernizing code I wrote in the past. This old code uses the class/delegate model and I am trying to transform them to use blocks as callbacks, not the delegate stuff.
What I do is to create a property like
#property (nonatomic, copy) void (^onTouch)(NSInteger index);
That would pass to the object using that class a block where code can be inserted and in this case executed on touch.
But my problem is this. When you use delegates and you have a method on the delegate protocol, Xcode will warn if you use that class and forget to implement the delegate protocols. Is that a way to do that with blocks? Or in other words: is there a way to make Xcode complain if a callback block is not defined by the caller?
I mean this would be the correct:
MyClass *obj = [[MyClass alloc] init];
obj.onTouch = ^(NSInteger *index){ //call back code to be executed };
This would be OK too
MyClass *obj = [[MyClass alloc] init];
obj.onTouch = nil;
but this would generate a message
MyClass *obj = [[MyClass alloc] init];
// no callback block defined.
Is this possible?
If you want to enforce setting a certain parameter, I would include it in the initializer.
MyClass *obj = [[MyClass alloc] initWithBlock:^(NSInteger *index) { /* code*/ }];
Then, in MyClass:
- (id)init {
// This will result in a runtime error if you use the wrong initializer.
NSAssert(NO, #"Use initWithBlock instead.");
}
- (id)initWithBlock(initWithBlock:^(NSInteger *)block) {
self = [super init];
if (self) {
self.onTouch = block;
}
return self;
}
Also note, attempting to execute a NULL block results in a crash, so make sure to do:
if (self.onTouch) { self.onTouch(); }
Wherever you run the block.
First, I strongly recommend defining types to represent your blocks - makes them a lot easier to work with, especially if you need to refactor the parameters.
You can't write code that distinguishes between "I set this property to nil" or "the runtime initialized this property to nil", at least not without some crazy runtime code to check the stack. Only option I can think of would be to use the null object pattern. Before I elaborate, bear in mind that I haven't actually tried to test this, but it should work. Define a block that means 'has no value' and set your property to point to that block on init. Then you can compare to that NullBlock at runtime to identify if someone explicitly set the property to nil (because it would be nil at that point) or gave it a real non-nil value.
Alternatively, if you don't mind manually writing your set accessors, you could have a BOOL that tracks if someone set the property explicitly. Then when you call the block just check if someone actually set the value or not.
#synthesize onTouchBlock=_onTouchBlock;
MyBlock _onTouchBlock;
BOOL _onTouchBlockWasSet;
- (void)setOnTouchBlock:(MyBlock)block {
_onTouchBlockWasSet = YES;
_onTouchBlock = block;
}
I would not recommend passing the value in the initializer because that makes it tied to the creation of that object type. If you wanted to change the block in code based on some condition, you'd be back to square one. Also, it prevents you from using storyboards which create that object.
Rookie question: I am writing a program that will generate a specific string and then display it in a text window in a different view controller. I have been testing to ensure that the code in fact generates the string using NSLog commands and I know the code is working as intended. For some reason it is not transferring across the view controller and I cant figure out why. Any help? Here is a snippet of the code:
CreateStoryViewController.m
- (IBAction)makeStory:(id)sender
{
StoryLine *myStory =[[StoryLine alloc] init];
[myStory setStory];
self.story = myStory.plot;
NSLog(#"story is %#", self.story);//this is generating the correct story string
self.displayStoryController = [[BIDDisplayStoryViewController alloc] initWithNibName:#"DisplayStoryView" bundle:nil];
[self.view insertSubview:self.displayStoryController.view atIndex:1];
}
DisplayStoryViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
BIDCreateStoryViewController *newStory = [[BIDCreateStoryViewController alloc] init];
NSLog(#"newStory.story is %#",newStory.story);//this generates null message
self.storyDisplay.text = newStory.story;
}
This is wrong. You're instantiating a new BIDCreateViewController object inside your second view controller. This is not the same as the original BIDCreateViewController object that pushed your second BIDDisplayStoryViewController.
You need to declare a string property in your BIDDisplayStoryViewController's header file.
Something like
#property (nonatomic, retain /*or strong, if using ARC*/) NSString *storyToDisplay;
Be sure to synthesize this in your implementation file as well.
When you create BIDDisplayStoryViewController inside your first view controller, you need to do it as follows:
self.displayStoryController = [[BIDDisplayStoryViewController alloc] initWithNibName:#"DisplayStoryView" bundle:nil];
self.displayStoryViewController.storyToDisplay = self.story;
Now inside your second view controller you can access this using self.myStory.
While this will solve your problem (and please do understand that it's not my intention to be rude here), I feel that there's a lack of understanding of how iOS (and OOP in general) works.
In your viewDidLoad method you are making a whole new story. This story is totally different from the one you made in the makeStory: method. You should add a StoryLine Property to DisplayStoryViewController.h, and set that after you init your displayStoryController.
make the intended variable a property type at .h file, so the other file can access it
Is there a way to make the compiler ignore this specific warning?
Here's what I do:
UIViewController *firstViewController = AppDelegate.instance.viewController;
//open the view of the clicked subItem
if ([firstViewController respondsToSelector:#selector(openView:inView:)]) {
[firstViewController openView:subItem.itemText.text inView:activeScreen]; //warning on this line
}
I know one way that works is to change UIViewController to ViewController (Name of it's class). But this fix won't work in the future, so I'm just looking for a way to ignore this warning.
It won't work in the future because, I'll be doing something like this:
//.m
UIViewController *firstViewController;
//.h
if (someCondition) {
firstViewController = AppDelegate.instance.viewController;
}
else{
firstViewController = AppDelegate.instance.otherViewController;
}
if ([firstViewController respondsToSelector:#selector(openView:inView:)]) {
[firstViewController openView:subItem.itemText.text inView:activeScreen]; //warning on this line
}
You should cast the object to the correct type where appropriate. Note that you can 'cast' to a protocol if you like. This gives you the safety of knowing that required methods are implemented without having to know the concrete type.
If you want to just have the compiler not complain, it's possible by calling performSelector:. But then you won't get compile-time checking.
[object performSelector:#selector(doSomething)];
See discussion: Using -performSelector: vs. just calling the method
If you want to pass exactly one object to your selector, it's possible by using the variant performSelector:withObject:.
If you want to pass multiple objects, you'll have to wrap them up in a container object, as described at iOS - How to implement a performSelector with multiple arguments and with afterDelay?.
In this case, you can just issue an explicit type conversion (cast):
UIViewController *firstViewController;
// ...
[(FirstViewController *)firstViewController openView:subItem.itemText.text inView:activeScreen];
Make sure to import the FirstViewController.h, so that method is known to the compiler. Tweak your code a bit:
UIViewController *vc = AppDelegate.instance.viewController;
//open the view of the clicked subItem
if ([vc respondsToSelector:#selector(openView:inView:)]) {
FirstViewController *firstViewController = (FirstViewController *) vc;
[firstViewController openView:subItem.itemText.text inView:activeScreen];
}
That should do the trick.
I have a ViewController, and GameController class which is just subclassed from NSObject. The view controller has one button linked to it and fires a IBAction that inits a GameController class. In the init of the GameController class is a CADisplayLink that adds one to an int named score. In the debugger the score is going up, but the label will only display 0.
in ViewController
-(void)setScore:(NSInteger)gameScore{
NSString *string = [NSString stringWithFormat:#"%i", gameScore];
scoreLabel.text = string;
NSLog(#"the score is %i", gameScore);
}
-(IBAction)play:(id)sender{
gameController = [[GameController alloc] init];
//in my header is GameController* gameController;
}
in GameController
-(id)init{
self = [super init];
if (self) {
viewController = [[ViewController alloc] init];
//in header ViewController* viewController;
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(gameLoop:)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode: NSRunLoopCommonModes];
//in the header CADisplayLink* displayLink;
}
return self;
}
-(void)gameLoop:(CADisplayLink *)sender{
deltaScore++;
if (deltaScore >= 20) {
deltaScore -= 20;
score++;
//deltaScore and score are NSIntegers
[viewController setScore:score];
}
}
score is not updated because you are creating new instance of view controller in game controller, you somehow need to pass the object of view controller to game controller and then use the same object to call method setScore.
Look carefully at your code. Your ViewController's ‘-play:‘ method creates a new GameController. Your GameController's ‘-init‘ creates a new ViewController. Is that really what you want?
You haven't given us much to go on, but it seems likely that either your game controller or your view controller should be "in charge" and create one instance of the other. There are about a million questions here on SO related to how to get one object to send some data to the another, but they all boil down to this: to pass information from object a to object b, a needs a pointer to b. A can then use that pointer to send messages to b.
So, for example, your view controller creates the game controller. That means the view controller has a pointer to the game controller, and it can use an instance variable or property to save that. If the game controller needs to send messages back to the view controller, it'll need a pointer to the view controller. The view controller can supply that pointer (to itself) when the game controller is created, or at some point afterward. To facilitate that, the game controller needs a method that can accept the pointer to the view controller. Maybe you decide to do it at creation, so you change the game controller's initialize toon method to ‘-initWithDelegate:(id)‘, define a GameControllerDelegate protocol, and then adopt that protocol in your ViewController class. You'd do this rather than just using ‘-initWithViewController:(ViewController*)‘ because its good style to prevent GameController from depending on the particular class of the object that's helping it -- GameController probably only cares that its helper implements a few methods, it doesn't need to know that it's helper is a ViewController.
In GameCenter the Game Score is of type long long int so use long long in your methods and return type if using anywhere.
When you want to so it on the label use
yourLabel.text = [NSString stringWithFormat:#"%lld",yourscore];
Usually, when the debugger gets information correctly but the UI isn't updating is because the operation to update the UI isn't performed on the main thread. Try this:
dispatch_async(dispatch_get_main_queue(), ^{ [viewController setScore:score]; });
%i is an invalid format specifier; use %d for signed 32-bit integer or %u for an unsigned 32-bit integer
Have you tried?
NSLog(#"the score is %lu", gameScore);
It should work for you.
Iam a newbiew to iPhone development. Version of my SDK is 2.2
In my code, UIViewController is used to change view dynamically once the app is launched, a method in the UIViewController is called to know which view should be initialized along with parameters, which goes to a 'switch-case' and assign a view to current view according to the parameter, like this:
case 1:
currentView = [[View01 alloc] init];
break;
case 2:
currentView = [[View02 alloc] init];
break;
and outside the switch-case:
[self.view addSubview:currentView.view];
I wonder f can pass a parameter along with initialization, like iniWithNibName or so? I need this because have to manipulate in the leaded view, according to the view from which its called.
Thanks.
One way to approach this is to modify your View01 and View02 classes to include an initWithParam: initialiser.
i.e. add
- (id) initWithParam:(NSString *)myParam;
to the #interface section and add
- (id) initWithParam:(NSString *)myParam {
if (self = [self init]) {
// handle or store 'myParam' somewhere for use later
}
return self;
}
to the #implementation section. Notice how the initWithParam: message internally calls the existing init. Obviously you could change the type, or number of parameters passed in as required.
Another approach would be to provide a property on your view class, so you could do something like the following:
currentView = [[View01 alloc] init];
currentView.myParam = #"SomeValue";
Which approach works the best will depend somewhat on your particular application needs.