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
Related
I have created a custom class for my UIBarButtonItem (refreshIndicator.m). This button will be on many different view controllers, all push-segued from my MainViewController/NavigationController.
Instead of dragging an outlet onto every single ViewController.m file for iPhone storyboard THEN iPad storyboard (ugh, still targeting iOS7), I want to know if there is a way to complete my task simply within my UIBarButtonItem custom class. I've looked around everywhere but I haven't quite found an answer to this,
All I need to do is check which UIViewController is present, check the last time the page was refreshed, and then based on that time, set an image for the UIBarButtonItem. (I've got this part figured out though, unless someone has a better suggestion). How can I check for the current UIViewController within a custom button class? Is this possible?
Does it need to know which view controller its on so it can tell that vc it was pressed? If that's the case, then use your button's inherited target and action properties. On every vc that contains an instance of the button, in view did load:
self.myRefreshIndicator.target = self;
self.myRefreshIndicator.action = #selector(myRefreshIndicatorTapped:);
- (void)myRefreshIndicatorTapped:(id)sender {
// do whatever
}
More generally, its better to have knowledge about the model flow to the views from the vc, and knowledge of user actions flow from the views. Under that principal, your custom button could have a method like:
- (void)timeIntervalSinceLastRefresh:(NSTimeInterval)seconds {
// change how I look based on how many seconds are passed
}
And your vcs:
NSTimeInterval interval = [[NSDate date] timeIntervalSinceDate:self.lastRefreshDate];
[self.myRefreshIndicator timeIntervalSinceLastRefresh:interval];
If you really must go from a subview to a view controller, you could follow the responder chain as suggested in a few of the answers here (but I would go to great lengths to avoid this sort of thing).
It is possible to achieve this, but the solution is everything but elegant. It is one way of getting around the basic principles of iOS and is strongly discouraged.
One of the ways is to walk through the responder chain, posted by Phil M.
Another way is to look through all subviews of view controllers until you find the button.
Both ways are considered a bad practice and should be avoided.
For your particular case, I would rethink the structure of having a separate instance of the bar button. For example, you could rework it into a single UIButton instance that gets displayed over every view controller and it can also act as a singleton.
In my app, some studies I've done show that when the user views an article, the vast majority of the time (85%+) they load the accompanying comments view controller that goes along with the article.
I'd love to load this view controller while they're reading the article, so when they tap the comments button to transition to the comments view controller the view controller is ready without any loading times.
How would I go about accomplishing something like this? At the moment when the user taps the button I call performSegueWithIdentifier: and pass it the identifier I set in the Storyboard.
And obviously, for the cases where the user decides to go back to the root view controller (say, a list of articles) I'd want to cancel the loading of that comments view controller as it would be wasteful to continue at that point.
If you keep the data model separate from the UI, you shouldn't have any trouble creating the views on the fly, and almost nothing to gain from creating them earlier.
A reasonably standard approach is to bring the view in with blank or filler data, and have the data call go out to the middle tier, with asynchronous handlers processing callbacks.
This is much easier with the block based completion handlers available in the iOS 7 flavored NSURLSession, and only slightly harder with NSURLConnection (which listens for responses on the main thread, but can be thrown into the background once you catch the response).
So my advice would be to focus on backgrounding the data calls and responses, and strongly differentiate between displaying UI and populating the UI with data. If your data manager is separate from your View Controller, nothing is stopping you from "pre-fetching" the data a little early, and then potentially having it ready when the ViewController needs it. It's a perfectly normal load balancing / customer experience technique for high value data.
The solution I'm about to describe is a bit of a hack - it doesn't really conform to the proper model-view-controller design pattern that Ryan mentioned. That being said, it might give you an idea about how to proceed. Perhaps you can improve on it to make it cleaner.
First, define a #protocol in the App Delegate. Let's call this protocol CommentQueryDelegate; it should define a method called -(void)handleCommentQuery. Also give your App Delegate a strong property to store the comment data and a weak property to store a delegate object, like so:
#property (nonatomic, strong) NSMutableArray* arrayOfComments;
#property (nonatomic, weak) id<CommentQueryDelegate> commentQueryDelegateObject;
Make sure to initialize the both of these properties to nil.
Somewhere in the article view controller, use dispatch_async() to asynchronously query your database and retrieve the comments while the user is reading the article:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
// query your database here
appDelegate.arrayOfComments = [NSMutableArray array];
[appDelegate.arrayOfComments addObject:#"someComment"];
[appDelegate.commentQueryDelegateObject handleCommentQuery];
}
If the query is completed before the user segues into the comments view, the handleCommentQuery message will be sent to the nil object, which will have no effect.
Now, in the comments view controller's viewDidLoad method, set the commentQueryDelegateObject property of the App Delegate to self. You will need to specify that the comments view controller conforms to the comment query protocol. Next, check to see if the App Delegate's arrayOfComments property is nil. If it isn't, great - display the comments immediately. Otherwise, display a UIActivityIndicatorView.
Implement the -(void)handleCommentQuery method in your comments view controller. This method should disable the activity indicator and display the comments.
One final thing to consider - the strong pointer to the arrayOfComments object will keep it from being deallocated, so you should set this pointer to nil once you're done with your article view controller.
What's the difference between declaring a UIButton in Xcode like this:
- (IBAction)testButton;
and declaring a button like this:
- (IBAction)testButton:(id)sender;
I understand that in the .m file you would then implement the buttons accordingly, as shown below:
- (IBAction)testButton
{
// insert code here..
}
and setting it up like this:
- (IBAction)testButton:(id)sender
{
// insert code here..
}
Is there any additional things you can do by declaring the button with :(id)sender, is there some additional stability, or is there no difference?
With :(id)sender you are able to access the button itself through the sender variable. This is handy in many situations. For example, you can have many buttons and give each a tag. Then use the [sender tag] method to find which button was tapped if many buttons are using this IBAction.
- (IBAction)someMethod:(id)sender {
// do stuff
}
Using (id)sender, you have a reference to who sent the method call. Please note, this doesn't have to be limited to a UIButton.
If you're created this method via control-dragging from the storyboard an only hooking up a single button, then sender is basically useless (it will always be the same), and should probably be marked as unused:
#pragma unused (sender)
(The compiler can better optimize your code if you do this.)
However, there's nothing wrong with hooking up several UI elements to the same IBAction method. You can then distinguish the sender via:
[sender tag]
...which returns an int that was either set via the storyboard or programmatically.
Moreover, you can call this method elsewhere in your class. You can either pass nil as the sender, or you can pass it a particular UI element in order to force it into the results you've coded for objects of that tag.
Nonetheless, if you plan to call the method with a nil argument, you can always throw:
if(!sender)
... into the method in order to handle special logic for when the method has been invoked programmatically as opposed to via user interaction.
It allows you to know which button you are working with. I have posted a simple example for a card game below
- (IBAction)flipCard:(id)sender {
[self.game flipCardAtIndex:[self.cardButtons indexOfObject:sender]];
self.flipCount++;
[self updateUI];
}
This method is used for a card flipping game. There are multiple buttons on the screen representing different cards. When you hit the button, a card in the model must be flipped. We know which one by finding the index of the variable sender
I'm facing some weird behavior in my map based App. I'm fetching some data to display a route using some directions service. It runs in an background thread using GCD. With the data fetched I return to the main thread to update the UI :
dispatch_async(dispatch_get_main_queue(), ^{
[self.mapProvider addToExistingPolyLinePoints:coordinates withTitle:#"line" removeOldOne:NO useCurrentIndex:NO];
[_distanceLabel setText:[NSString stringWithFormat:#"%.2lf km",[self.draggingLogic getOverallDistance]]];
[self.progress setHidden:YES];
});
This all works fine in my RouteViewController. But if I go back to the RootViewController using the back button and reenter the RouteViewController and refetch the whole thing, the UI does not get evaluated. It shows the same behavior as if the UI update is not done in the main thread. The data arrives correct.
I'm wondering if it is some kind of issue regarding the view controller life-cycle of iOS, which I did not got completely. What happens when I push the back button. Obviously the ViewController is not destroyed but if I reenter it will create a new one. Is it possible from the RootViewController to determine if an instance of the target view controller is existing and perform the Segue using it?
Anyway, I'm not sure if this is regarding my issue.
Thanks for any ideas
If I understand right what you wrote, you create a new controller every time you "enter" but the dispatching block always refer to the first one you create, so the new one is displayed but the old one get the notifications...
There are lots of way to avoid this, depends on your implementation, but a simple solution may be keep a (strong) reference to the map view controller in a property of the root view controller: if it's nil (first time) you create the map controller and do all the needed stuffs, else you'll simply show it, without the creation part.
example code, in .h:
#property (strong,nonatomic) MyMapController* mapController;
in .m:
if (!self.mapController)
{
// create the controller and the update handler...
self.mapController = ... //created object
}
// show it and everything...
hope this help
You are needed to do the stuff given here....
you create a new controller every time you "enter" but the dispatching block always refer to the first one you create, so the new one is displayed but the old one get the notifications...
Ex-code, in Interface file :
#property (strong,nonatomic) MyMapProvider* mapProvider;
And in implementation file :
if (!self.mapProvider)
{
self.mapProvider = ... //create object
}
// do your stuff..
So I'm struggling with Core Data. I'm finding there are many ways to do the same thing, and that if you try to build the app using Storyboards and UIManagedDocuments you need to read all the tutorials and examples older than last year with a translation sheet. Today's question is about finding the best practice when adding a new managed object. I've seen examples done both ways:
Create the new managed object in a table view controller (after clicking +) and giving that new shining managed object to the subordinate "Add" view controller to get user input for all the object attributes. This seems simple, and the returned object is simple to understand because it contains all the individual attributes. But I've seen example code in this "Add" view controller for a "cancel" button that deletes the managed object passed in and then calls Save Context before dismissing itself. Functional, but the MVC training gnome on my shoulder is screaming at me about having this subordinate Add View delete an object and horrors directly call Save Context. The Recipe example code from Apple appears to use this method.
Send nothing to the Add view controller, and have it send back a delegate call the table view controller that returns each of the attributes as a separate passed parameter. So the return method becomes really long: controller:(UIViewController *)controller didReturnFirstName:(NSString *)firstName andLastName:(NSString *)lastName andStreetAddress:(NSString *) and... and... and.. But this is SO consistent with MVC dogma because the managed object is created back in the table view controller when it receives all the individual attributes, and the "Add" view never touches the Model(Core Data), or throws away an unused managed object when the user changes their mind.
Even with chained delegated methods, I'm still debating with myself which is a better method. Comments and ideas from those who've lived with both forms would be a welcome addition.
Thanks.
It you look at the example in Apple's tutorial, they accomplish this task by doing a number of things outlined below, in this case they have a modal view appear to input the information that is to be added to the data model:
In the modal view that appears, they create a protocol to handle either dismissing the view or saving the data and a property of type id that implements that protocol to be the delagate, this insures whatever object that is implments the required methods
In the view controller that created the modal view, they implement the protocol, including saving the object to the data model and dismissing the modal view
In the view controller that created the modal, they set the view controller that created the modal view as the modal view delegate during the seque to the modal
So, to summarize, in the modal view to collect the new data you need to:
create a protocol and property in .h and synthesize it
#protocol yourProtocol <NSObject>;
//methods that determine what happens based on what user does, it would save your core data object
#end
#property(nonatomic, weak) id<yourProtocol> delegate;
Then, in the modal view .m file you call those methods on the delegate, likely when they pick save or done, so a method for each probably as IBAction connected to a button
[self.delegate myMethod];
In the view that presented the modal view, you implement the protocol in the .h file
#interface viewController() <yourProtocol>
and finally, add your methods to the view controller that presented the modal view's .m file, this should include removing the view and saving your core data. According to Apple and other sources, the view controller that caused the popup/modal, etc... should be the one that dismisses it. Then, in the seque using the seque indentfying, set the view controller that is presented int he modal view as the modal view's delegate.
You're right, there are many approaches to take on this one.
It sounds as if you are starting with a context that may not be saved, so in order to be able to get back to your starting point I would tackle it like this:
Start by creating a new NSManagedObject to be used as a temporary object which gets passed to your "Edit" view.
If you are editing an existing object, copy the attributes from the existing object into this new temporary object (You can use a quick for loop to copy them all generically). Otherwise proceed with the newly created object.
Pass the temporary object to the "Edit" view controller and have it treat both cases the same.
a. If the user presses cancel, have either a protocol or delegate method which notifies the tableview and the tableview then simply discards the temporary object.
b. If they press save, notify the tableview and then copy the attributes from the temporary copy back to the original and delete the temporary object (if it is an edit operation), or just leave it as the newly created object if it was an "Add" operation.
This is not really an answer so feel free to ignore, but neither is any of the others (so far) a complete answer, and I feel the need to add some points without creating a new question, because I really want to get to the bottom of this too. In particular the answer should deal with
what happens when you Cancel vs Save
what if you want to use the same controller for editing an existing entity rather than creating a new one
Nicks answer deals with apple's (current) convention for passing data back, but is not a complete answer because that tutorial does not deal with core-data managed objects, and apples own samples that do use managed objects do it differently.
But on this subject, I find that the delegate convention is unwieldy in some cases and was happy to read this from a more experienced developer:
http://robsprogramknowledge.blogspot.pt/2012/05/back-segues.html
Rob details different mechanisms for that which are all equally valid, including the protocol/delegate convention (Nick's), using a data object without a protocol (so-called "shared memory") - more like some of the others suggested, and - my favourite - using a block.
A block is a nice option because, while it works like a delegate, the code remains entirely in context in the "parent" viewcontrollers source
However, the "shared memory" makes more sense because, as you say, using anything other than the data (managed) object which you've already designed to pass all of these properties around just seems silly.
The issue for me then is, what's the convention for creating these managed objects, passing them around, then either
1. saving changes
2. canceling changes
3. canceling creation of a new entity altogether
Inafzigers answer above works something like this, but why all that copying of properties? Can't I just not save my changes?
Apple' CoreDataBooks seems to deal with this by creating a child ManagedObjectContext and a new entity in that context (insertNewObjectForEntityForName) in the prepareForSegue, and in the delegate method (when it comes back):
- if Save was clicked, it saves both this new ManagedObjectContext and the parent FetchedResultsContext to store the object
- if Cancel was clicked it does nothing, effectively discarding the new managed object context, and thus, presumably, the new object
So this might be the conventional approach, except that:
There is a comment in the code of that sample (CoreDataBooks RootViewController.m) which states that the two managed contexts are not necessary, implying that you might be able to do the same with just one. Here's the full comment:
IMPORTANT: It's not necessary to use a second context for this. You could just use the existing context, which would simplify some of the code -- you wouldn't need to perform two saves, for example. This implementation, though, illustrates a pattern that may sometimes be useful (where you want to maintain a separate set of edits).
CoreDataBooks does not use a UIManagedDocument - not sure if that makes a difference
What's not clear to me is, do we really to use insertNewObjectForEntityForName to create the managed object? What about just creating the object and then only inserting it later if the user hit Save?
Also what about if we do an Edit and a Cancel - can we use the undo manager to get rid of changes?
I think the right approach would actually be to create a new, separate NSManagedObjectContext for your AddController (if a new item is to be created) or EditController (if an existing item is to be modified).
Chose A) or B) depending on your SDK:
A) You then have the choice to either save changes in that context, or discard the context.
In case of a "save", you can merge the changes into the TableController's ManagedObjectContext through Notifications (NSManagedObjectDidSaveNotification).
In case of a 'cancel", you can just discard the separate context.
B) Alternatively, if you're on OSX 10.7(+), you could use nested NSManagedObjectContexts, creating an NSManagedObjectContext in AddController (the child context), and settings it's parentContext to the TableController's NSManagedObjectContext.
In case of a "save", you'd save the child context, and the parent context.
In case of a "cancel", you'd just discard the child context.
See a detailed example of this in Apple's CoreDataBooks example http://developer.apple.com/library/ios/#samplecode/CoreDataBooks/Introduction/Intro.html