I have game that is played on a single view, when the game is over I want the user to be able to press a button (play again) that will completely reload the view (clearing all game data and refreshing the view as if it were loading for the first time). I have tried
[self.view setNeedsDisplay]
however nothing is happening. Do I have to manually clear out the data or is there a way to reset everything at once?
What I've similarly done in this case is to create a property which holds all of your game subviews, etc.
#property (nonatomic, strong) UIView *gameView;
Then in viewDidLoad, we call a method that sets up our game view for the first time (You will see we will use the same method a little later again for resetting the game)
- (void)viewDidLoad
{
[super viewDidLoad];
[self setUpGame];
}
- (void)setUpGame
{
// Your game views (subviews, buttons, etc.) set up here
self.gameView = [[UIView alloc] initWithFrame:self.view.bounds];
}
Then, when the user taps the play again button, we simply remove the game view from the superview, and call the prior method we discussed, which sets up the game again. Your button's target should be set to call this method below: [self playAgain];
- (void)playAgain
{
[self.gameView removeFromSuperview];
// This is the method we previously discussed above
[self setUpGame];
}
It's up to you to think of some cool and unique animations to make it a pleasing resetting of the game at this point :)
setNeedsDisplay just indicates that you would like iOS to redraw the screen, which you rarely should need to call manually.
I would probably implement something like #troop231 already stated, which is a reset method, but I would not re-allocate any buttons / views etc because that could be costly. In the MVC model, you should have your data (scores, # of lives, etc.) stored separately and your views should just reference them. So, reset the model and assuming you have your views aware of model changes, they will update accordingly.
Ways to do this include KVO, NSNotification, Core Data's NSFetchedResultsController (probably overkill), delegation, etc.
Related
My app has a search view(search bar) which is used all over the app. I don't want to create duplicated code so I created a view controller called MySearchViewController to handle the search job, then I created a singleton object in AppDelegate. In every view controller, I added my search view like this:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self.view addSubView:search.view];
}
My questions, Is it a good way? It's a singleton so it can be added to many views. Do I need to remove the view from last view before adding to current view?
Understand that you are mixing some concepts that are not necessarily related: avoid duplicated code and Singletons.
Wikipedia says this about singletons:
In software engineering, the singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.
The most important characteristic of a singleton (in my humble opinion) is that the object is instantiated only once and every single place in your application will use the same instance. Well, to use your search feature everywhere and avoid duplicated code you don't need the search view to be instantiated only once, maybe the data that comes with it, but not the view itself.
Two better ways of achieving this:
1 - You can create a ViewController with your search and just embed this on the other views using a Container View, you can use blocks or a delegate protocol to communicate between your controller and the view that is embedding it.
2 - You can create a Parent class of the ViewController that will include the search bar, like a SearchViewController and all the other viewControllers that needs the same feature will inherit from it.
The singleton could be useful if you are planing to share the same search data and text between all the ViewControllers of the application, but it would be a singleton only with these information, the UISearchBar and all other view elements should not be part of the singleton.
Ideally, you should instantiate a fresh instance of MySearchViewController every time when you want to add it to another view to avoid problems.
Do I need to remove the view from last view before adding to current view?
Its not required to remove it from previous super view because whenever you add this singleton MySearchViewController's view to some other view, it will automatically gets removed from last super view and now its super view is your new view where you have added it.
If you want to add a view from a different view controller, your view controller has to be that view controller's parent view controller:
- (void)viewDidLoad
{
MySearchViewController* search = [AppDelegate searchViewController];
[self addChildViewController:search];
[self.view addSubView:search.view];
}
also, make sure that when the search.view is added, it is already initialised.
Why you do not use NSObject class ?, i do not know your requirement , but if you want to store latest updated value in whole project(in execution) then you should use the singleton, but if you do not want to store value (i mean one result for whole project) then you should use NSObject derived Class. advantage is singleton consumes memory so memory will be wasted. NSObject class will be reusable and only allocated when it is required and then ARC will take care of all things. If you want to know how to create NSObject and use of it then you can give me reply.
Here is some code to load a XIB as part of a custom object with the object gets initialized.
Why are you not creating custom search component for search?
you can use this component all over the app.
also this is not creating duplicat code.
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[[[NSBundle mainBundle] loadNibNamed:#"SearchView" owner:self options:nil] objectAtIndex:0];
[self addSubview:self.view];
self.frame = self.view.frame;
}
return self;
}
Please check below code. Hope this is work for you.
- (void)viewDidLoad
{
if ([self.view viewWithTag:123456]) {
[[self.view viewWithTag:123456] removeFromSuperview];
}
MySearchViewController* search = [AppDelegate searchViewController];
search.view.tag = 123456; // give a any random tag to view
[self.view addSubView:search.view];
[self addChildViewController:search];
}
Please make sure given tag is not assign to other object except search.view in self.view.
Thanks
I'm trying to change the title of a button after I call back from a notification but it doesn't respond at all. I checked it's not nil and checked the text Im' assigning and all is good. I made the property type strong instead of weak but no success.
- (void) setButtonTitleFromSelectedSearchResult:(NSNotification *)notif
{
[self popController];
self.sourceMapItem = [[notif userInfo] valueForKey:#"SelectedResult"];
NSLog(#"The Selected Result is: %#", self.sourceMapItem.name);
//Testing
NSLog(#"%#", self.fromButton); // check it's not nil
[self.fromButton setTitle:self.sourceMapItem.name];
}
With WatchKit, if a user interface element isn't currently visible, it cannot be updated. So, if you've presented another interface controller "on top", you can't update any of the presenting controller's interface elements until you've dismissed the presented controller. At that point, you can safely update the presenting controller in its willActivate method.
SushiGrass' method of passing blocks is certainly one valid approach. In my testing, however, I ended up having to manage multiple blocks, and many of the subsequent blocks reversed what earlier queued blocks had accomplished (for example, first changing a label's text to "foo", then "bar", then "foo" again. While this can work, it isn't optimal.
I'd suggest that anyone who is working on a WatchKit app takes a moment to consider how they want to account for off-screen (i.e. not-currently-visible) interface elements. willActivate is your friend, and coming up with a way to manage updates in that method is worthwhile if you're moving from controller to controller.
For what it's worth, I've encapsulated a lot of this logic in a JBInterfaceController subclass that handles a lot of this for you. By using this as a base class for your own interface controller, you can simply update your elements in the added didUpdateInterface method. Unfortunately, I haven't yet had the time to write proper documentation, but the header files and sample project should get you going: https://github.com/mikeswanson/JBInterfaceController
I'm using latest XCode 6.3 and below code working with me.
self.testBtn is bind with Storyboard and its WKInterfaceButton
I also have attached screenshot with affected result.
I'm setting initial text in - (void)willActivate
- (void)willActivate {
[super willActivate];
[self.testBtn setTitle:#"Test"];
[self performSelector:#selector(justDelayed) withObject:nil afterDelay:5.0]
}
-(void)justDelayed
{
[self.testBtn setTitle:#"Testing completed...!!"];
}
If you're using an IBOutlet for the property fromButton be sure that is connected to WKInteface on the storyboard, like below:
I solved this kind of issue by creating a model object that has a property that is a block of type () -> (Void) (in swift). I create the model object, set the action in the block that I'd like the pushing WKInterfaceController to do on completion, and finally pass that model object in the context to the pushed WKInterfaceController. The pushed WKInterfaceController holds a reference to the model object as a property and calls it's completion block when it's done with whatever it needs to do and after func popController().
This worked for me for patterns like what you are describing along with removing rows on detail controller deletion, network calls, location fetches and other tasks.
You can see what I'm talking about here: https://gist.github.com/jacobvanorder/9bf5ada8a7ce93317170
I'm developing IOS messanger app, I have inbox(tableview) in which I have cells(conversations) and when I select a conversation, I would like to present this conversation(tableviewController full of messages), but i dont like how much time it takes to present this controller. So my idea was to create whole controllers(tableviewController full of messages) objects before selecting conversation, and then just push them. First time I select conversation, it is blank, after going back and then selecting it again, it work. Problem is obvious, some variables are initialized in viewDidLoad method. I have tried to move them to init method but then every time conversation was blank.
Do you have any experiences with this? Any hint will be appreciated a LOT.
Thank you!!!
in tableviewController full of messages:
.h file:
#property (nonatomic, assign) BOOL firstAppear;
.m file
self.firstAppear = NO; //in init method
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
if (self.firstAppear) {
//add a indicator view here
}
}
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
if (self.firstAppear) {
//get tableView data here, then [tableView reloadData] to show data
//remove the indicator
self.firstAppear = NO;
}
}
It sounds to me like you are doing premature optimization. Creating and pushing a table view controller should take a small fraction of a second. If it's taking longer, something is wrong. Are you loading the conversation data from a remote server or something?
You might want to use Instruments to figure out what is taking extra time and causing a delay. Then you can focus on the actual cause rather than guessing.
I have a UITableViewCell subclass which contains a UIStepper. When the user interacts with the stepper, I fire off an NSTimer so that the stepper will save its value and write to Core Data if the value is not changed again within 2 seconds.
To put it another way:
UIStepper is contained within UITableViewCell.
User can change stepper value up and down.
Each touch triggers timer with 2 second delay.
Each subsequent touch invalidates the timer.
When stepper has been left alone for 2 seconds, changes are saved.
This works very well for what I need it for. The problem is that if my user is very quick and they change the value, then pop the view controller and go to do something else, the 2 second timer still hasn't fired and the data is not up to date for the next action.
To keep things as simple as possible, I need to be able to tell within that table view cell if the (table) view is getting popped. Then I can expedite the saving process and ensure data is up to date and saved before any other actions take place.
If you want to tell if the table has been popped, put your cleanup/save method in the viewWillDisappear method. Because you're using timers, you do not want to do it it dealloc, so you don't have any unintended strong reference cycles.
It's not clear from your question, but I would want to make sure that you're not putting your NSTimer on your UITableViewCell cells. Obviously, it's a model issue, not a view issue, but also table views do all sorts of optimizations for dequeue and reusing table view cells.
Second, what ever object class you have to keep track of your data (I call it ModelDataItem) should provide not only the mechanism to save, to use the timers, etc., but also a mechanism to force the save of any pending records (which I do through a boolean needSave). So, to support that, in my mind, you ModelDataItem probably should have at least the following four items:
(a) a reference to its own timer;
#property (nonatomic, strong) NSTimer *timer;
(b) a flag that indicates whether the record has a pending save operation
#property (nonatomic) BOOL needSave;
(c) a method that you call whenever the object's values change (e.g. the value was incremented) to schedule the save in 2 seconds:
- (void)scheduleSave
{
self.needSave = YES;
if (self.timer)
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:#selector(save)
userInfo:nil
repeats:NO];
}
(d) you need the method that the timer calls that actually saves the record:
- (void)save
{
// do whatever you need to save the record
NSLog(#"%s saving value=%#", __FUNCTION__, self.value);
// now let's clean up the timer
if (self.timer)
{
[self.timer invalidate];
self.timer = nil;
}
self.needSave = NO;
}
Then, in your table view controller, you should:
(a) when the stepper's UIControlEventValueChanged is called, you should obviously change your data model and then call the above ModelDataItem method scheduleSave;
(b) when the table view is being dismissed, should presumably promptly save anything pending:
for (ModelDataItem *item in allModelDataItems)
{
if (item.needSave)
[item save];
}
Note, on that last point, I do not rely on dealloc to clean up and save the model items that need saving, because a scheduled NSTimer retains its target and thus the dealloc won't get call (or at least not until the timer is performed). So, I manually iterate through them and take care of that when I dismiss the view.
You could have your custom cell listen for a notification that the table view is going away and then post that notification in the viewWillDisappear method of your view controller.
When the parent TableView is popped of of the NavigationController that I assume you're using, the TableView should be dealloc'ed. In addition, each of its TableViewCells should also be dealloc'ed.
You could invalidate the timer and process any pending updates inside of the TableViewCell's dealloc method.
Without changing the design, that's what I would try. If that proves to be problematic, you might also try setting up some sort of communication back to the TableView when this value changes and have the TableView be responsible for the data updates.
It's hard to tell from your description, but I don't think you need to use the timer at all. Can't you just assign that stepper value to a property or ivar of that class, and then in the viewDidDisappear method, write that value out to core data?
I've found the best way to do this in the past is to set the UITableViewCell Subclass as a delegate of the view controller... then in your view controllers viewDidDisappear just call the delegate method.
The delegate method would be implemented within your UITableViewCell Subclass and would simply call the same code that you do when your two second timer completes. This is similar to the notification method suggested by Phillip, but a little bit cleaner if you're used to assigning delegates, etc.
Just override dealloc method and do the stuff. UITableViewCell is deallocated when parents view is popped / unloaded.
I'm playing around with drawing in iOS apps. I have a class that is a subclass of UIView that draws some lines and stuff. When the user presses a button, I instantiate the class and do an addSubView on the view of the main UIViewController of the app. The more times the user presses that button, the more instances of that class get added to the view. It's working just fine.
Now I want to provide the user a way to delete one of those views. So far I've put a [self removeViewFromSuperview] into the touchesBegan method of the custom UIView. So when the user presses the drawing it gets removed from the view. But, it's not actually deleted, right? Since the view was instantiated within the method that executes when the button is pressed I have no way to reference it from within the UIViewController. What's the appropriate way to make sure I'm not wasting memory with a UIView that was created and removed?
On a related note, if I was to put a toggle switch on the main window's UIView that toggles delete, how can I check from within touchesBegan if that toggle switch is set to delete=yes? Would I have a some sort of boolean variable in the AppDelegate that I can check from within the UIView subclass? How would I reference that?
Thank you for your help,
Stateful
If you add the view like this:
UIView *viewBeingAdded = [[[UIView alloc] init] autorelease];
[view addSubview:viewBeingAdded];
You can remove it without leaking memory:
[theViewAboutToBeRemoved removeFromSuperview];
Regarding the UISwitch, you don't need to keep its value anywhere unless you need it for something else. You can access its value directly:
if ([theSwitch isOn]) { ... }
You don't even need an IBOutlet, you can access the switch with its tag:
UISwitch *theSwitch = (UISwitch *)[view viewWithTag:<# switch tag number #>];
if ([theSwitch isOn]) { ... }
In this case you must set a unique tag number for the switch in Interface Builder or when you create it.
When you do [mainView addSubView:myView], mainView will retain myView. If you created myView with alloc/init, then you retained it also. If you don't need myView after adding it to the main view then simply do [myView release] after adding it. When you remove it from the main view, it will get released and deallocated.
If you create the UIView with alloc/init, add it to the superview then release the view, the superview will retain it. When it is removed with removeViewFromSuperview it will be dealloc'ed.
I typically autorelease a view after adding it, leaving the parent the only reference.
As to checking a toggle, you could add an IBOutlet so you can inspect it directly. (This may not be pure MVC, but I don't know if putting it in [UIApplication sharedApplication].delegate is necessarily cleaner.)