I have a UITableView combined with a fetched Results Controller. I deployed controller will change content method, and set the delegate of the frc to self.
But in a function I want to nil out it's delegate so that the will change content delegate method won't be called, and change delegate to self again after some operations to make sure other methods work right, just like this:
-(void)function
{
self.frc.delegate = nil;
for (id obj in self.frc.fetchedObjects) {
if ([obj isKindOfClass:[MultiValue class]]) {
MultiValue * multiValue = (MultiValue *)obj;
multiValue.isSelected = [NSNumber numberWithBool:YES];
}
}
self.frc.delegate = self;
}
The problem is, the delegate method (controllerWillChangeContent) will be called after the function method, so after I set self.frc.delegate to self, the delegate method will still be called.
How to solve this? Many thanks.
First of all:
Delegating is a way to customize the behavior of objects. It has similarities with subclassing on a per-instance basis. Would you change the hierarchy of an instance's class while the object is living? (Yes, you would do in some situations – very rarely.)
So the delegate of an object is not a "state" of the object, you should change. It is something like the "kind" of an object. You should recheck your whole approach.
Second of all:
The reason is that all changes are collected and the delegate message is sent afterwards, when you reset the delegate. This behavior is better in most situations. From the docs:
Rather than responding to changes individually (as illustrated in
Typical Use), you could just implement controllerDidChangeContent:
(which is sent to the delegate when all pending changes have been
processed) to reload the table view.
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/index.html
To your Q:
You should recheck, why you do not want to be informed about changes in a specific situation while you want to be informed in other situations. This looks like a structural smell. If you are really in such a situation, you should recheck this again. If you are still in such a situation, you can set a flag inside the delegate that signals to ignore a change.
Related
I am using a UITabBarController, and my 3rd tab observes an array on a singleton data store (implemented in viewDidLoad).
Currently if I just log out (and change root view controller from App Delegate), the app will crash when dealloc is called on that 3rd tab with the message "cannot remove observer for the key path "X" because it is not registered as an observer.
Using breakpoints, I see that viewDidLoad is never called on this 3rd tab, however dealloc is being called when I sign out. What is going on? I assume the UITabBarController is holding a reference to the 3rd tab when I enter the storyboard, but does not "load" that tab. Yet iOS calls dealloc on it when I release the tab bar controller.
Should I use a boolean to track viewDidLoad execution, or try to remove the observer with a #try statement? Is there an overall better design for this?
Do not use #try. Exceptions in Objective-C should always be considered programmer error, and should be fatal.
As you say, use a boolean ivar set in -viewDidLoad to avoid this.
The view has not been loaded because views are only loaded when they are required for display.
Raw KVO can be dangerous and unwieldy. While not required to answer this question, ReactiveCocoa significantly improves the KVO experience.
viewDidLoad is called before the view appears for the first time. UITabBarController is creating the relevant UIViewController, but the view is not loaded during creation. It is loaded on-demand, when a user visits the tab for the first time.
KVO removal is problematic, I don't think you can avoid using #try in dealloc. I would suggest to use KVOController: it's fairly easy to use and it would also handle all the edge cases for you.
May have found an even better solution. I add the observer in the method initWithCoder:(NSCoder *)aDecoder, which is called when the parent UITabController is loaded. I am using the storyboard which may be why I need to call override this method instead of regular init. Doing this now without the need for a BOOL flag or #try and no crashing.
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[anObject addObserver:self forKeyPath:aKeyPath options:0 context:NULL];
}
return self;
}
Use a flag to set whether or not KVO has been set up. Using #try can create memory management issues depending on the state of the app.
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
Say I write a UITextField subclass and want to have control over the text written into it by the user. I would set the input field's delegate to be myself and implement -textField:shouldChangeCharactersInRange:replacementString:.
However, I would still want to allow whatever part of code uses me as a text field to implement the usual delegate methods. An approach for that would be to store a second delegate reference and map them like so:
- (id)init {
self = [super init];
super.delegate = self;
return self;
}
- (void)setDelegate:(id)delegate {
self.nextDelegate = delegate;
}
- (id)delegate {
return self.nextDelegate;
}
I would then proceed to implement all UITextFieldDelegate methods and forward them to the next delegate as I wish. Obviously, I may want to modify some parameters before passing them on to the next delegate, like in -textField:shouldChangeCharactersInRange:replacementString:.
Another problem I'm thinking of is when the user's sets nextDelegate to the text field itself (for whatever reason), resulting in an infinite loop.
Is there a more elegant way to hijack delegate callbacks like in the example code I posted?
The problem with your approach is the overridden delegate accessor: There's no guarantee that Apple's code always uses the delegate ivar directly and does not use the getter to access the delegate. In that case it would just call through to the nextDelegate, bypassing your sneaked in self delegate.
You might have checked that your approach works in the current implementation but this could also change in future UIKit versions.
Is there a more elegant way to hijack delegate callbacks like in the example code I posted?
No, I'm not aware of any elegant solutions. You could not override the delegate accessor and instead set up secondary delegate (to which you have to manually pass all delegate messages).
To solve the actual problem of filtering text input it might be worthwhile looking into
- (void)replaceRange:(UITextRange *)range withText:(NSString *)text;
This method is implemented by UITextField (as it adopts UITextInput) and could be overridden to filter the text argument.
I think you're thinking about this correctly, and the approach you outlined will work fine (I've done it).
There's no circularity issue because you shouldn't expose nextDelegate in the subclass's public interface, so no caller will have the chance to setup a cycle. (You could also test in the setter that delegate != self.
It would be better, though, if you could avoid this altogether. For example, if you just want to tweak the text field text as it changes, you can get the control event:
[self addTarget:self action:#selector(didChange:) forControlEvents:UIControlEventEditingChanged];
Then,
- (void)textFieldDidChange:(id)sender {
self.text = [self alteredText];
}
- (NSString *)alteredText {
// do whatever transform to user input you wish, like change user input 'a' to 'x'
return [self.text stringByReplacingOccurrencesOfString:#"a" withString:#"x"];
}
This will work as well, but with the odd side effect that the delegate won't see the alteredText in shouldChangeCharactersInRange:. That's fixable by making alteredText public and having the class customers call it instead of the standard getter.
All of the problems with subclassing can be avoided by using a different approach of intercepting delegate messages: A "delegate proxy".
The idea is to use an intermediate object (derived from NSProxy) that either responds to a delegate message or passes it along to the next delegate. It's basically what you did by subclassing the UITextField but instead of using the text field object we'll use a custom object that handles only the interception of some delegate messages.
These customized delegate proxys form a set of reusable building blocks which are simply plugged into each other to customize the behavior of any object that uses delegation.
Here's an example (code on github) of a chain of delegates:
UITextField -> TextFilterDelegate -> SomeViewController
The UITextField passes delegate messages to TextFilterDelegate which responds to textField:shouldChangeCharactersInRange:replacementString: and passes other delegate messages on to its own delegate (the view controller).
I have a method in AppDelegate which get some data from server, this method get called every time when application become active. I want to reload some table in another view when server data received successfully. How can i do this without using NSNotification?. I know passing notification can do this job. I want to know is there any other way to perform this?
well comments by users already explained what to do.
But i had another approach.I don't prefer, but it will work.
Always set some constant tag value to your table view (say 1001).And make sure than you never use the same tag on others.
Then in that method of appDelegate, you can do->
UITableView *tableView =(UITableView*)[self.window viewWithTag:1001];
[tableView reloadData];
I am reminding you again, don't use this. use NSNotification class.
Although it creates an unneeded dependency between the view and the app delegate, I'd implement this way:
create a ViewReloader protocol with a method - (void) reloadTable;
implement that protocol in the view
add a property of type id<ViewReloader> to the app delegate
when the view is instantiated, assign it to the property defined above
when you need to reload, call the reloadTable method of the id<ViewReloader> property from the app delegate (but always check for property != nil)
if the view is destroyed/deallocated, remember to reset the app delegate property
Well samething you can do with using custom delegates as well. If you want to pass message from one object to another. You can use notification also, but use only when you want to broadcast the message.
I am writing a custom subclass of UITableView. I would need this object itself to be its own data source and delegate, and this subclass would then have its own data source and delegate. This is done primarily so I can intercept calls to the datasource and delegate and potentially augment them before sending them off to their actual datasources.
My class is defined as so.
CustomTableView : UITableView<UITableViewDelegate, UITableViewDataSource> {
...
id customDataSource;
id customDelegate;
}
The problem comes when I try to set my data source and delegate.
I would like to override uitableview's properties:
- (void)setDataSource(id<UITableViewDataSource>)ds {
[super setDataSource:self]
customDataSource = ds;
}
Basically, I would like to tell the parent class(UItableView) to set the data source to self. I would then forward any callbacks to the customDataSource, after I have modified them.
[super setDataSource:self] doesnt crash, but the datasource never gets set. Does anyone have any ideas? Thanks
I ended up not needing to use the method proposed in this question, but I did get it working. The problem was that I had accidentaly synthesized the properties that needed overriding, namely dataSource and delegate.
For people who need to do this in the future, simply override setDelegate and setDataSource in your custom subclass.
Dont assign the datasource to self. Create an intermediate object, which you contain in your CustomTableView, and set the datasource to that. Call it DataSourceInterceptor or something.
Another way to accomplish this would be to method-swizzle the datasource object that is being set.