Storing contextual information in UIActionSheet - ios

When creating a UIActionSheet to prompt a user to delete an item from a list, I currently have to maintain the deleted item (or at least its index in the list) as an instance variable in my view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath row] == 4) {
// Delete button pressed
_deletingItemIndex = [indexPath section];
UIActionSheet actionSheet = ...
}
}
Then when the UIActionSheet closes, if the user decided to delete the item, I have to reference that _deletingItemIndex variable, then set it to -1 or some other nil value.
What I would like to do, is maintain either the deleting item, or its index, in the actual UIActionSheet without needing to subclass UIActionSheet.
I find it strange that the delegate method for UIActionSheet provides the sheet to the delegate, but you can't store any contextual information (not even a dictionary) in the sheet itself.

There are quite a few categories out there for adding block-based dismiss handlers to UIActionSheet and UIAlertView now. I personally use Mugunth Kumar's UIKitCategoryAdditions.
This would let you do the following...
[UIActionSheet actionSheetWithTitle:#"Hooray" message:#"Blocks Are Awesome!" buttons:#[...] showInView:self onDismiss:^(int buttonIndex) {
//Now you have access to all your local variables here!
} onCancel:^{
//And here!
}];

You should use objc_setAssociatedObject()
Have a look at http://darkdust.net/writings/objective-c/attaching-objects for some code.

UIActionSheet is a system object which should only be concerned with presenting a user with choices and getting a button index out.
Using an instance variable of view controller also doesn't make sense, because this isn't really a state of view controller, but rather of a current action which started existing the moment user started deleting.
The correct pattern, thus, is to create a new object, save information there, and use as a delegate:
MyDeletionAction *action = [MyDeletionAction actionWithIndex:[indexPath section]];
UIActionSheet actionSheet = ...
actionSheet.delegate = action;
...
and somewhere else, define MyDeletionAction as a class implementing the delegate protocol.
An added bonus of this approach is that you can take the code out of your view controller into separate classes, which is a good thing. Moreover, it's likely that your MyDeletionAction, MyInsertionAction etc. will share some common code.
Perhaps even the presence of action sheet should be an implementation detail of your action. For example, what if you provide an option to delete without confirmation?
Note also that in this approach someone should retain an action object, for example by object retaining itself until the action is fully completed, or by using a VC instance variable for this purpose:
self.lastAction = [MyDeletionAction actionWithIndex:[indexPath section]];
[self.lastAction start];
This also allows you to remember the state of last action for possible postprocessing.
start here appears because my actions usually have this kind of inheritance: MyDeletionAction <- MyAction <- NSOperation. Your mileage may vary.

Related

WKInterface button doesn't change title

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

What's the difference with :(id)sender?

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

How to handle concurrency by queue/NSThread for UI design, iOS dev

What I am trying to achieve is simple, from first thinking though. I found it hard to handle at last.
I would like to push a table view as a selection list, user select one cell and the cell string was sent to the previous view as selected string, simple huh??
See two pictures first
what bothers me is that:
I would like to provide (at least) two buttons, one on the left is back button auto-generated by navigation controller, and the right one is for editing. And the navigation controller is defaulted to have two buttons (from my knowledge). So there is no place for "Done" button, which is supposed for user to tap and then confirm and pop to the previous view.
So, when the user tap a cell, "Wearing" for example, I would like the following to happen, automatically and visually SEEable for user:
user can SEE that "Housing" cell is unmarked
then user can SEE that "Wearing" cell is marked
then after a little time gap (say 0.2 second), pop to the previous view and update the selection, automatically.
At first I thought it's easy but it's definitely not. Here is my code for doing it, but working wired
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0ul);
dispatch_async(queue, ^{
//unmark previous cell
if (selectedIndexPath!=nil) {
[[self.tableView cellForRowAtIndexPath:selectedIndexPath]setAccessoryType:UITableViewCellAccessoryNone];
}
selectedIndexPath=indexPath;
//get the selected cell to mark
UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:indexPath];
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
dispatch_sync(dispatch_get_main_queue(), ^{
//wait a little
[NSThread sleepForTimeInterval:0.2];
//return to previous view
NSLog(#"here.........");
if ([objectToUpdateCategory respondsToSelector:#selector(updateCategoryTo:withSelectedIndexPath:)]) {
NSLog(#"sending.......... update info");
[objectToUpdateCategory updateCategoryTo:cell.textLabel.text withSelectedIndexPath:selectedIndexPath];
NSLog(#"sent update info");
}
[self.navigationController popViewControllerAnimated:YES];
});
});
The tricky thing is that if I put [self.navigationController popViewControllerAnimated:YES]; to the last, the view will not visually update the unmark and mark step and go back to the previous view immediately. At first, when I didn't consider the unmark thing, the “queue" stuff in code can do the mark step visually before popping back, but sometimes not working. I don't know if my code is correct, actually I don't quite understand this queue tech from apple. But I'm pretty sure it has something to do with NSThread / queue or else that handle concurrency. I've checking Apple documents for a whole day and found no direct answer.
Hope someone could help me on this, thanks in advance :)
To "after a little time gap (say 0.2 second), pop to the previous view", use the performSelector:withObject:afterDelay: methods or one of its variants, e.g.:
[self performSelector:#selector(delayedPop) withObject:nil afterDelay:0.2];
and put the popViewControllerAnimated in the delayedPop method, e.g.:
-(void)delayedPop{
[self.navigationController popViewControllerAnimated:YES];
}
First of all, as I wrote in my comment, you shouldn't update the UI on a background thread. This will cause a lot of problems, including the UI not being updated immediately. In your case you don't need to use dispatch_async or dispatch_sync at all. What I would do is create a property in the view controller that displays the categories table view:
#property (nonatomic, weak) id<CategoryControllerDelegate> delegate;
When you push the category controller on the stack you set your expense controller as the delegate. Then, when the user makes a selection in the category controller, you call a method on the delegate (defined in a protocol), for example the one in your code sample:
#protocol CategoryControllerDelegate<NSObject>
#optional
- (void) updateCategoryTo: (NSString*) category withSelectedIndexPath: (NSIndexPath*) path;
#end
After that you pop the current view controller off the stack.

iOS: Three20: TTTableItem Delegate: How to identify tableItem uniquely?

(I posted the question below on the Three20 forum, but no response so far! Maybe you guys can help out.)
I use the TTTableViewController in my iOS app and there is one thing I wish to achieve.
Each of the TTTableItem classes has a method to assign a delegate & selector when initializing it.
Eg: TTTableTextItem has (+itemWithText:delegate:selector:) Now, the selector method would hav a signature like this: -(void)selectorMethodForTableItem:(id)sender; I would like to identify uniquely the tableItem that was selected, but within the selector method I only have the TTTableTextItem object, which only has the 'text' parameter. (The 'text' parameter is a string and it could be the same for 2 tableItems)
I can easily solve this if I provide a TTUrl for each tableItem, but I do not want to navigate to a new screen when a tableItem is pressed. Just say, I want to display an alert based on which tableItem a user selects.
How do I solve this problem? Do I have to define a custom TTTableItem class?
you can set a TTURL for each of your TTTableTextItem and "catch" the touches in the TTTableViewController, before TTNavigtor takes over, so your app won't navigate to a different page.
in your TTTableViewController, add this function:
///////////////////////////////////////////////////////////////////////////////////////////////////
- (void)didSelectObject:(id)object atIndexPath:(NSIndexPath *)indexPath {
TTTableItem* item = object;
NSLog(#"%#", item.URL);
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
Note this function doesn't call the parent didSelectObject, meaning it won't pass the object to the TTNavigator.

How to best design my list-detail viewcontrollers?

My app has a list of clients in a table view. When you click on a client it takes you to a detailed view controller using a standard navigation controller.
The list view controller allows the user to swipe to delete a client. The detailed view controller has a button to delete a client.
When deleting a client I want to present an action sheet with a several choices.
THE QUESTION: I don't want to duplicate code in both of my view controllers for presenting the action sheet and handling the results of the action sheet. As both view controllers are deleting a client, the code is identical in both instances. Is there a design pattern that is considered best practice in this case?
Thanks for any help.
I guess you can't really use one-set of code for UIActionSheet in both viewControllers. But for a very similar situation, my approach was as follows. I hope it helps.
I create my own custom class, e.g. MySortingClass (in my case, it was sorting options, e.g. date ascending / descending, name ascending / descending). This class is a subclass of NSObject.
In my custom class I declare various methods that will return, for example, an array of options title to show to the user, an array of NSSortDescriptors, etc.
In any of my viewControllers that I need to present a list of sorting options to user, I would import MySortingClass, alloc, init, and get an array of options, show them via actionSheet, and send back the response as an index to the MySortingClass and get the appropriate NSSortDescriptor back and re-sort.
...
// In MySortingClass
- (NSArray *)arrayOfOptions;
- (NSSortDescriptor *)sortDescriptorForSortingOptionIndex:(NSInteger)index;
...
...
// In a ViewController
MySortingClass *msc = [MySortingClass alloc] init];
...
- (void)actionSheet:(UIActionSheet *)actionSheet didDismissWithButtonIndex:(NSInteger)buttonIndex
{
...
[anArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:[msc sortDescriptorForSortingOptionIndex:buttonIndex]]];
...
}

Resources