I am trying to start using MVVM with Objective-c but I get some problems with CoreData. I don't know who should handle the fetchedResultsControllerDelegate methods. The viewModel or the viewController?
I think that the viewModel should handle it, but I see too much code to do the same.
Let's say you have an edit button in a view. Edit button is hidden when there are no objects in fetchResultsController and you want to show it when any objects are added.
If you implement NSFetchedResultsControllerDelegate in viewController:
ViewController gets notifications about changes of model
ViewController must notify ViewModel that something happened with the
model
ViewModel has to change value of the editButtonShown
property
ViewModel must notify the viewController to update the
view
If you implement NSFetchedResultsControllerDelegate in viewModel:
model notifies the viewModel about the change
viewModel changes the editButtonShown
property and sends notification to viewController
If you implement these methods in viewController then it gets reference to the model and this should not happened in MVVM
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
I think that correct solution is to implement the NSFetchedResultsControllerDelegatein ViewModel. Then you should have similar protocol to notify the viewController to update the view, but it should work with viewModels, not with the models
Related
Hi I've been googling and I can't find an answer and maybe there's a different way to do this So I'm putting it to the community.
I have a tableview in a UIViewController. The UIViewController is the datasource and delegate for the table view. I then have a second controller which reacts to scrolling in the main UIViewController. Ideally I'd want the second controller to also be a delegate so that scrollviewDidBeginScrolling will fire in both controllers. I want to do this because it makes controller 2 very easy to implement because you'd set it as the delegate and pass in the tableview reference and it would do all the heavy lifting.
Basically can you pass an array of delegates to tableView.delegate? I could see a few situations where you'd want multiple controllers or views to react to an event like scrollViewDidBeginScrolling. Is there any way to accomplish something similar without having to do stuff like
-(void)scrollViewDidBeginScrolling:(UIScrollView *)scrollView{
[anotherViewController scrollviewDidScroll:scrollview];
[otherView scrollViewDidScroll:scrollview];
}
I'm using it for a controller than handles Pull To Refresh for tableviews and I want to make implementation as easy as possible with as few lines/methods in the tableview controller as possible.
You can accomplish what you need but you will need to chain the delegates. The table view will only have one delegate but you can define your own custom delegate method in the view controller that is the delegate of the tableview and have your other view controller set itself to be the delegate of that. Another brute force method would be to just send out notifications and have the view controllers register for the notification.
In you UIViewController1 delegate methods, just send the same call to UIViewController2 delegate methods. E.I.:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[delegate2 tableView:tableView didSelectRowAtIndexPath:indexPath];
// etc...
Edit for comment below. You could subclass UITableView and give it multiple delegate properties or an array of delegates property.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
for(id delegate in _delegates)
[delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
// Do nothing else, because the delegates handle everything.
}
What you need is delegate multiplexing. I wrote a class to do just this: https://github.com/aleph7/MultiDelegate
I am fiddling with the new UICollectionView and the UICollectionViewLayout classes. I have created a custom layout, subclassing UICollectionViewFlowLayout.
My cell sizes are changing dynamically and I set the item sizes using the delegate method below
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"SETTING SIZE FOR ITEM AT INDEX %d", indexPath.row);
return CGSizeMake(80, 80);
}
Now, under the prepareLayout method of my custom UICollectionViewFlowLayout class, I need to access these size variables so that I can make calculations how to place them and cache them for layoutAttributesForItemAtIndexPath.
However, I can't seem to find any property under UICollectionView or UICollectionViewFlowLayout to reach the custom item sizes I set in the delegate method.
Found it myself.
Implement the custom class like without omitting UICollectionViewDelegateFlowLayout
#interface SECollectionViewCustomLayout : UICollectionViewFlowLayout
<UICollectionViewDelegateFlowLayout>
and then you can call
CGSize size = [self collectionView:self.collectionView
layout:self
sizeForItemAtIndexPath:indexPath];
Looking at the various UICollectionView... header files, and watching the WWDC 2012 Session 219 - Advanced Collection Views and Building Custom Layouts video (from about 6:50 onwards), it seems the extensible delegate pattern takes advantage of dynamic typing to ensure the layout can properly access its extended delegate methods.
In short...
If you define a custom layout with its own delegate, define that delegate protocol in the layout's header file.
Your delegate object (typically the UI(Collection)ViewController that manages the collection view) should declare itself to support this custom protocol.
In the case that your layout is just a UICollectionViewFlowLayout or subclass thereof, this just means declaring conformance to UICollectionViewDelegateFlowLayout.
Feel free to do this in your class extension in the .m file if you'd rather not #import the layout header into the delegate's interface.
To access the delegate methods from the layout, call through to the collection view's delegate.
Use the layout's collectionView property, and cast the delegate to an object conforming to the required protocol to convince the compiler.
Don't forget to check that the delegate respondsToSelector: as usual prior to calling optional delegate methods. In fact, if you like, there's no harm in doing this for all methods, as the typecasting means there is no runtime guarantee the delegate will even implement the required methods.
In code...
So if you implement a custom layout that requires a delegate for some of its information, your header might look something like this:
#protocol CollectionViewDelegateCustomLayout <UICollectionViewDelegate>
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath;
#end
#interface CustomLayout : UICollectionViewLayout
// ...
#end
Your delegate declares conformance (I've done so in the implementation file here):
#import "CustomLayout.h"
#interface MyCollectionViewController () <CollectionViewDelegateCustomLayout>
#end
#implementation
// ...
- (BOOL)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)layout
shouldDoSomethingMindblowingAtIndexPath:(NSIndexPath *)indexPath
{
return [self canDoSomethingMindblowing];
}
// ...
#end
And in your layout's implementation, you access the method like this:
BOOL blowMind;
if ([self.collectionView.delegate respondsToSelector:#selecor(collectionView:layout:shouldDoSomethingMindblowingAtIndexPath:)]) {
blowMind = [(id<CollectionViewDelegateCustomLayout>)self.collectionView.delegate collectionView:self.collectionView
layout:self
shouldDoSomethingMindblowingAtIndexPath:indexPath];
} else {
// Perhaps the layout also has a property for this, if the delegate
// doesn't support dynamic layout properties...?
// blowMind = self.blowMind;
}
Note that it's safe to typecast here, as we're checking the delegate responds to that method beforehand anyway.
The evidence...
It's only speculation, but I suspect it is how Apple manages the UICollectionViewDelegateFlowLayout protocol.
There is no delegate property on the flow layout, so calls must go via the collection view's delegate.
UICollectionViewController does not publicly conform to extended flow layout delegate (and I doubt it does so in another private header).
UICollectionView's delegate property only declares conformance to the 'base' UICollectionViewDelegate protocol. Again, I doubt there is a private subclass/category of UICollectionView in use by the flow layout to prevent the need for typecasting. To add further weight to this point, Apple discourages subclassing UICollectionView at all in the docs (Collection View Programming Guide for iOS: Creating Custom Layouts):
Avoid subclassing UICollectionView. The collection view has little or no appearance of its own. Instead, it pulls all of its views from your data source object and all of the layout-related information from the layout object.
So there we go. Not complicated, but worth knowing how to do it paradigm-friendly way.
There is a swift version:
self.collectionView(self.collectionView, layout: self.collectionView.collectionViewLayout, sizeForItemAtIndexPath: indexPath)
Check out UICollectionView-FlowLayout on GitHub. Same idea, this just makes accessing the extended delegate methods of flowLayout a little cleaner.
For the later readers, IOS 7 has UICollectionViewFlowLayout which has defined it.
In my case everything about layout, cell layout etc. is being defined inside nib for UIViewController and separate nib for UICollectionViewCell. MyCollectionViewCell contains UIImageView with autolayout to cell with padding/margins but square-shaped.
I need round icons instead squared but don't want to take care which nib I use for iPhone or for iPad (I have separate nibs for devices and for orientation as well).
I don't want to implement #selector(collectionView:layout:sizeForItemAtIndexPath:) into my view controller.
So, inside collectionView:cellForItemAtIndexPath:
I can just use
CGSize size = cell.imageView.bounds.size;
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = size.height/2.0;
Because collectionView:layout:sizeForItemAtIndexPath: call before collectionView:cellForItemAtIndexPath: and layout done.
You can check round avatars on the bottom
I'm new to iPhone development. I've been reading several questions on how to make a google maps annotation callout window accept line breaks. Every tutorial I've read requires me to fire the mapView:didSelectAnnotationView method. But I have no idea how to trigger this. things I've tried include
putting the method in my MapViewController.m file which extends UIViewController
putting the method in a MapView.m file which extends MKMapView, then have my Mapview element in my storyboard reference it as the class to use
There's so much about xcode, objective c, and iphone development that I don't understand, so i can't tell where my problem lies.
At the moment, my map does plot my desired marker on the desired location. I just need to understand how to fire the mapView:didSelectAnnotationView and mapView:viewForAnnotation functions before I can start customizing the call out box.
Does anyone have step by step instructions on how to trigger these functions?
A bit of background
A few things to note:
You don't call mapView:didSelectAnnotationView. The MKMapView calls that function on it's delegate. In other words, when you set up an MKMapView, you tell it: "hey, listen, anytimme you need to tell me what's happening on the map, go tell this guy, he'll handle them for you". That "guy" is the delegate object, and it needs to implement mapView:didSelectAnnotationView (that's also why its name "did select", ie, it already happened, as opposed to "select"). For a simple case, the delegate is often the UIViewController that owns the MKMapView, which is what I'll describe below.
That method will then get triggered when the user taps on one of your annotations. So that's a great spot to start customizing what should happen when they tap on an annotation view (updating a selection, for instance).
It's not, however, what you want if you want to customize what annotation to show, which is what it sounds like you're actually after. For that, there's a different method just a few paragraphs earlier on the same man page: mapView:viewForAnnotation. So substitute this method if you find that mapView:didSelectAnnotationView isn't what you were looking for.
What you can do
If you got as far as a map with a marker, I'm guessing you have at least:
* a view controller (extendeding from UIViewController, and
* an MKMapView that you've added to the view for that view controller, say named mapView
The method you want to fire is defined as part of the MKMapViewDelegate protocol.
The easiest way to get this wired is to:
make your UIViewController the delegate for you MKMapView
in code, say in your viewDidLoad, of your MapViewController.m you could do mapview.delegate = self, OR
in Interface Builder, you could drag the connection from the the MKMapView delegate property to the file's owner
then, define a method on your UIViewController called mapView:didSelectAnnotationView, declaring it just like the protocol does, in your MapViewController.m file:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
// whatever you need to do to your annotation and/or map
}
Good luck!
mapView:didSelectAnnotationView is a delegate method of the map view, you can read about it here:
MKMapViewDelegate Protocol Reference
You don't need to call it, the map view will call it "by it self" and send it to every view/view controller that registered as it's delegate.
What do you need to do
Basically you need to add the MKMapViewDelegate on your .h file, what will look something like this:
#interface someViewController : UIViewController <MKMapViewDelegate>
Then in the .m file, after you instantiate the map view you should add:
mapView.delegate = self;//were self refers to your controller
From this point and on your controller will be able to "receive messages" from the map view which are the methods that you can see on the MKMapViewDelegate reference I linked to.
So to implement the mapView:didSelectAnnotationView you need to add in your .m file
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view{
//if you did all the steps this methosd will be called when a user taps the annotation on the map.
}
What is happening
What happens in the background is:
The map view has a method (Apple codded) that handles the AnnotationView touch events.
When a touch event take place it sends a "message" to all of it's delegates saying "Hey a user did Select Annotation View on this map, Do with it what ever you need".
usually it looks like that:
[self.delegate mapView:someMapView didSelectAnnotationView:someAnnotationView];
Then every view/controller that assigned itself as a delegate and implemented the method will cal this method.
Good luck
Place *place = [[Place alloc] init];
PlaceMark *placeMark = [[PlaceMark alloc] initWithPlace:place];
[self.mapView selectAnnotation:placeMark animated:YES];
I have a text field cell in a table view, from which I need to be made aware when it ends editing. I thought I would set my Controller class as the text field cell's delegate, and then use NSTextField's delegate method textDidEndEditing:, but realized that the text field cell doesn't seem to have delegate methods? Why is this, and what can I do (other than subclassing) to be informed when editing is finished?
Thanks
NSTextFieldCell inherits from NSCell (well, technically from NSActionCell which inherits from NSCell). The NSCell class is used to (from the docs):
The NSCell class provides a mechanism for displaying text or images in an NSView object without the overhead of a full NSView subclass.
Notably, The cell class is used for "displaying text or images", and not dealing with interaction with the user. Similarly, with the NSTextField class:
The NSTextField class uses the NSTextFieldCell class to implement its user interface.
The NSTextField deals with the actual user input, whilst using the text field cell to simply implement its user interface, and similarly, the delegate methods to provide notification when the editing of text has ended is provided through the NSTextField class and not through the NSTextFieldCell class.
If you want to be notified of when editing ends in an NSTableView, then you need to register yourself as an observer of the NSTextDidEndEditingNotification (you might want to read the NSNotificationCenter class reference if you are unfamiliar with notifications). To do this, place the following in your controller class; the awakeFromNib function is a good place to include it to ensure that it is called upon your application's startup:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(textDidEndEditing:)
name:NSTextDidEndEditingNotification
object:tableView];
Where tableView is the pointer to your NSTableView object. Then, simply implement the method as follows:
- (void)textDidEndEditing:(NSNotification *)aNotification
{
// Do what you want here
}
Don't forget to remove yourself as an observer upon deallocation:
- (void)dealloc
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self];
}
The reason that you set the object that you are observing to be the NSTableView instance (and not the cell itself) is that under the hood, when you edit a cell in the table, the cell that you are dealing with isn't being edited directly; it is the window's (or a custom) field editor. When editing ends, the field editor then passes the new value for that cell on to the table view. However the table view will post a notification to say that a cell has finished being edited.
Implement the tableView:setObjectValue:forTableColumn:row: method in the NSTableViewDataSource protocol. Put it next to the tableView:objectValueForTableColumn:row: method that you've already implemented.
- (void)tableView:(NSTableView *)aTableView
setObjectValue:(id)anObject
forTableColumn:(NSTableColumn *)aTableColumn
row:(NSInteger)rowIndex
{
[mutableArrayWithStrings replaceObjectAtIndex:rowIndex withObject:anObject];
}
I have a map annotation view that contains a rightcallout button which loads an accessory view which is a UIViewController class. I am using resuable annotations, but am wondering how I can pass updated information to my UIViewController class. Let's say I have 2 string values which map to 2 UILabels on my view. How can I update those values after the initial accessory view has already been loaded into memory as a resusable view?
Any help would be appreciated.
You'll need to maintain a reference to the UILabels in the object that gets the update, and then use setTitle: (I think) to update the labels.
In your annotation subclass you need to override the setTitle method to send the changes to the instance of your UIViewController class that your subclass is holding. Or, you could setup your annotation subclass to receive notifications (from NSNotificationCenter), and upon receiving a notification, update the title and the instance of your UIViewController class.
If you are unfamiliar with NSNotifications, then here is a quick reference. I used these to keep my annotations updated.
NSNotification Example
Try using the MKMapViewDelegate method:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control;
This method is called when a user tapped one of the annotation view’s accessory buttons. Assuming that your MKMapViewDelegate is also the UIViewController that can access your accessory view.