I want to draw a chart in an UIView. The question is how do I get the data (Points) to the view. If I create a protocol and set the UIViewController as the delegate where in the UIView do I call the delegate methods (initWithFrame? might be to early, and the delegate might not be set, awakeFromNib? but the view is 100% created in code, it has no nib file) ..
initWithFrame? might be to early, and the delegate might not be set.
In fact, the delegate cannot be set by the time you're in initWithFrame:, since the first thing you do with an object after allocation is initialization, i. e. until the init method returns, you can't call (well, it's idiomatic not to do so, at least) any other methods.
What you have to do is have a loadData or reloadData method, that the delegate must call explicitly after having set itself as the delegate of your view. I. e., from the view controller, you can call it like this:
#implementation ChartViewController
- (id)init
{
if ((self = [super init])) {
chartView = [[ChartView alloc] initWithFrame:self.view.frame];
chartView.delegate = self;
[self.view addSubview:chartView];
[chartView reloadData];
}
return self;
}
Then, in your chart drawing view, implement - reloadData as follows:
- (void)reloadData
{
// Call the delegate here,
// then do the drawing
}
A better way is to use UIViewController instead of a UIView. Because your that view has to manage data. Managing data is a UIViewController's job.
make a protocol but dont call it delegate. call it dataSource :D
anyways, call it when you first need the data .... as late as possible.. NOT in init.. maybe in the setDataSource call.
or when you draw for the first time and see you have no data..
look at UITableView to see how he does it and imitate that
Related
I have a UICollectionView that's connected to myclass datasource and delegate. When I call [self.collectionview reloaddata] it works normally but after a while (random time and number of times) when I reload the collection again it doesn't work any more.
collection reference delegate and datasource are correctly defined.
self.devicesCollectionViewDataSource= devices;
self.devicesCollectionView.delegate = self;
self.devicesCollectionView.dataSource = self;
[self.devicesCollectionView reloadData];
Overwrite the setDelegate method of your collection view subclass, set a breakpoint and observe who's changing it.
-(void)setDelegate:(id<YOUR_PROTOCOL>)delegate {
_delegate = delegate; // breakpoint here -> look at the trace log, it should give you the caller that's overwriting your delegate
}
Clarification: this doesn't solve your problem per se, but at least you know where to look
I'm trying to implement state restoration in an app that uses iOS 6+ and storyboards, but I am having problems finding a way to prevent duplicate calls to heavy methods.
If I simply start the app, then I need to setup the UI in viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
}
This works fine in a normal, non-state-restoration world. Now I've added state restoration and after restoring some properties I need to update the UI with those properties:
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
// restore properties and stuff
// [...]
[self setupUI];
}
So what happens now is that first the setupUI method is called from viewDidLoad, and then again from decodeRestorableStateWithCoder:. I don't see a method that I can override that's always called last.
This is the normal order of method calls:
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
When using state restoration, this is called:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder
viewWillAppear
viewDidAppear
I can't place the call to setupUI in viewWillAppear because then it would also be executed every time you native back to a view.
It would be much handier if decodeRestorableStateWithCoder was called BEFORE viewDidLoad because then you could use restored properties. Sadly that not the case, so... how can I prevent doing the work in viewDidLoad when I know that I need to do it all over again in decodeRestorableStateWithCoder right after?
If you're doing state restoration programatically (i.e. not using storyboards), you can use + viewControllerWithRestorationIdentifierPath:coder:, init the view controller there and use whatever you need from the coder to do your pre-viewDidLoad initialization.
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
if ([[identifierComponents lastObject] isEqualToString:kViewControllerRestorationIdentifier]) {
if ([coder containsValueForKey:kIDToRestore]) {
// Can only restore if we have an ID, otherwise return nil.
int savedId = [coder decodeIntegerForKey:kIDToRestore];
ViewController *vc = [[ViewController alloc] init];
[vc setThingId:savedId];
return vc;
}
}
return nil;
}
I've found that trying to implement state restoration has shown up bad programming practices in my code, like packing too much into viewDidLoad. So while this works (if you're not using storyboards), the other option is to refactor how you're setting up your view controllers. Instead of using a flag, move code pieces to their own methods and call those methods from both places.
#property (nonatomic) BOOL firstLoad;
- (void)viewDidLoad {
[super viewDidLoad];
self.firstLoad = YES;
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.firstLoad) {
[self setupUI];
self.firstLoad = NO;
}
}
Thanks to #calvinBhai for the suggestion.
Funny enough the decoding sequence is even different and exactly:
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
and it totally makes sense like this.
From the book "Programming iOS 9: Dive Deep into Views, View Controllers, and Frameworks" pages 386-387
The known order of events during state restoration is like this:
application:shouldRestoreApplicationState:
application:viewControllerWithRestorationIdentifierPath:coder:
viewControllerWithRestorationIdentifierPath:coder:, in order down the chain
viewDidLoad, in order down the chain; possibly interleaved with the foregoing
decodeRestorableStateWithCoder:, in order down the chain
application:didDecodeRestorableStateWithCoder:
applicationFinishedRestoringState, in order down the chain
You still don’t know when viewWillAppear: and viewDidAppear: will arrive, or whether viewDidAppear: will arrive at all. But in applicationFinishedRestoringState you can reliably finish configuring your view controller and your interface.
Yes, it would indeed be nicer if -decodeRestorableStateWithCoder: were called before -viewDidLoad. Sigh.
I moved my view setup code (which depends on restorable state) to -viewWillAppear: and used dispatch_once(), instead of a boolean variable:
private var setupOnce: dispatch_once_t = 0
override func viewWillAppear(animated: Bool) {
dispatch_once(&setupOnce) {
// UI setup code moved to here
}
:
}
The documentation states that "views are no longer purged under low-memory conditions" so dispatch_once should be correct for the lifetime of the view controller.
Adding to berbie's answer,
The actual flow is:
initWithCoder
+viewControllerWithRestorationIdentifierPath:coder:
awakeFromNib
viewDidLoad
decodeRestorableStateWithCoder:
viewWillAppear
viewDidAppear
Be aware that inside initWithCoder, you need to set self.restorationClass = [self class]; This will then force viewControllerWithRestorationIdentifierPath:coder: to be called.
I noticed that setting the splitViewController.delegate in willFinishLaunchingWithOptions causes viewDidLoad to be called even earlier. So if you move that to both didFinishLaunchingWithOptions then you can successfully configure your view controller inside - (UIViewController *)application:(UIApplication *)application viewControllerWithRestorationIdentifierPath:(NSArray<NSString *> *)identifierComponents coder:(NSCoder *)coder before viewDidLoad is called. It might be useful for you to do it there anyway since you'll have access to AppDelegate objects like persistentContainer.viewContext rather than need to register that object with restoration so it could have been accessed by reference in the ViewController's - (void)decodeRestorableStateWithCoder:(NSCoder *)coder.
One correction to MixedCase flow (which was very helpful, thank), the actual call flow is a bit different :
This is the normal order of method calls:
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
When using state restoration, this is called:
viewControllerWithRestorationIdentifierPath (decode any data that is needed for regular start-up)
awakeFromNib
viewDidLoad
viewWillAppear
viewDidAppear
decodeRestorableStateWithCoder (decode restorable state data, and set your controller UI)
-(id) initForNewItem{
self = [super initWithNibName:#"EmployeeDetailViewController" bundle:nil];
if(self){
[nameField setDelegate:self];
[pinField setDelegate:self];
}
return self;
}
In this case, the delegates are not successfully set. However, if I go into the xib file and ctrl-click + drag the UITextFields onto the file owner and set them as delegates through that, it works. Can someone explain why calling setter methods in the init method does not work?
Inside init method, the views haven't been loaded. All the visual setup must be done further on in the viewController's lifecycle, at least into the viewDidLoad method. Or in case that you don't go for a .xib solution, you can do it while loading the view manually, into the loadView method.
So, both labels are nil into the init method, and the delegate can't be set.
I spent much time to get a better understanding in delegation in Objective-C. I got it working for most cases, but there is a problem in a specific case, which I find difficult to understand. Let me explain what I am trying to do:
I have a custom view called GridLayoutView, which is subclass of UIView. I also have a view controller SomeViewController, which is the delegate of GridLayoutView.
I have a custom initWithFrame method, and I am conditionally calling another initialization method baseInit. That method calls a delegate method at some time. Here is some code from GridLayoutView:
//
// Delegator
// GridLayoutView.m
//
#implementation GridLayoutView
- (id)initWithFrame:(CGRect)frame
numberOfRows:(NSUInteger)rows
numberOfCols:(NSUInteger)cols
{
self = [super initWithFrame:frame];
if (self) {
self.numberOfRows = rows;
self.numberOfCols = cols;
self.numberOfCells = rows * cols;
if (self.numberOfCells > 0) [self baseInit];
}
return self;
}
- (void)baseInit
{
// do some more initialization stuff here
// ...
// then call a delegate method
[self.delegate someMethod:someObj];
// However, this method is not called because self.delegate is nil
}
and some code from SomeViewController:
//
// Delegate
// SomeViewController.m
//
#implementation SomeViewController
// ...
// in some method
self.gridLayoutView = [[GridLayoutView alloc] initWithFrame:gridLayoutFrame
numberOfRows:rowsCount
numberOfCols:colsCount];
self.gridLayoutView.delegate = self;
// ...
The delegate method never gets called within baseInit, because the delegate is nil at that time and it gets set after initWithFrame and baseInit methods are done. I have confirmed this.
I sense that there is something wrong in my workflow of delegation. I have a solution but I don't think it is the best way to go. The solution is basically passing the SomeViewController instance to the delegator by modifying the initWithFrame method such as:
- (id)initWithFrame:(CGRect)frame
numberOfRows:(NSUInteger)rows
numberOfCols:(NSUInteger)cols
delegate:(id<GridLayoutViewDelegate>)aDelegate
This approach works, but I am uncomfortable due to passing SomeViewController to GridLayoutView in its initWithRect. I am wondering if this is a good way to go with delegation or is there a better approach? I would be very grateful if someone can clear this for me.
If I'm understanding you correctly, there aren't many options here.
Modifying your initializer (as you suggested) to pass in the delegate. There is nothing wrong with that, don't know why you don't like it.
Remove the dependency on the delegate during initialization and instead, send whatever delegate message is appropriate when the delegate property is set by overriding the setter:
- (void)setDelegate:(id<GridLayoutViewDelegate>)aDelegate
{
_delegate = aDelegate;
// send whatever message makes sense to the delegate
[_delegate someMethod:object];
}
EDIT - noticed your comment
Your initialization method should not take any significant amount of time. It's unclear what you mean by 'loading views'. If you simply mean creating and adding subviews to a view then that is fast and there should be no need to communicate progress to a delegate (which you can't do anyway b/c the initialization is on the main thread and UI won't update until all of init is complete).
If you mean loading data that takes a long time, you should disconnect that from initialization and load the data in a background operation, sending progress messages to a delegate.
i would implement the setDelegate function and then call
[self someMethod:someObj]; from there
Suppose you implement a custom table view and a custom view controller (which mostly mimics UITableViewControllers behaviour, but when initialized programmatically, ...
#interface Foo : MyCustomTableViewController ...
Foo *foo = [[Foo alloc] init];
... foo.view is kind of class MyCustomTableView instead of UITableView:
// MyCustomTableView.h
#protocol MyTableViewDelegate <NSObject, UITableViewDelegate>
// ...
#end
#protocol MyTableViewDataSource <NSObject, UITableViewDataSource>
// ...
#end
#interface MyCustomTableView : UITableView
// ...
#end
// MyCustomTableViewController.h
#interface MyCustomTableViewController : UIViewController
// ...
#end
How should you implement/override init methods in correct order/ways so that you could create and use an instance of MyCustomTableView both by subclassing MyCustomTableViewController programmatically or from any custom nib file by setting custom class type to MyCustomTableView in Interface Builder?
It important to note that this is exactly how UITableView (mostly UIKit for that matter) works right now: a developer could create and use either programmatically or by creating from nib, whether be it File owner's main view or some subview in a more complex hierarchy, just assign data source or delegate and you're good to go...
So far I managed to get this working if you subclass MyCustomTableViewController, where I will create an instance of MyCustomTableView and assign it to self.view in loadView method; but couldn't figure out how initWithNibName:bundle:, initWithCoder:, awakeFromNib, awakeAfterUsingCoder:, or whatever else operates. I am lost in life cycle chain and end up with a black view/screen each time.
Thanks.
It is a real mystery how the UITableViewController loads its table regardless of if one is hooked up in interface builder, however I have came up with a pretty good way to simulate that behavior.
I wanted to achieve this with a reusable view controller that contains a MKMapView, and I figured out a trick to make it happen by checking the background color of the view.
The reason this was hard is because any call to self.view caused the storyboard one to load or load a default UIView if didnt exist. There was no way to figure out if inbetween those 2 steps if the user really didn't set a view. So the trick is the one that comes from a storyboard has a color, the default one is nil color.
So now I have a mapViewController that can be used in code or in storyboard and doesn't even care if a map was set or not. Pretty cool.
- (void)viewDidLoad
{
[super viewDidLoad];
//magic to work without a view set in the storboard or in code.
//check if a view has been set in the storyboard, like what UITableViewController does.
//check if don't have a map view
if(![self.view isKindOfClass:[MKMapView class]]){
//check if the default view was loaded. Default view always has no background color.
if([self.view isKindOfClass:[UIView class]] && !self.view.backgroundColor){
//switch it for a map view
self.view = [[MKMapView alloc] initWithFrame:CGRectZero];
self.mapView.delegate = self;
}else{
[NSException raise:#"MapViewController didn't find a map view" format:#"Found a %#", self.view.class];
}
}
The strategy I've used when writing such classes has been to postpone my custom initialization code as late as possible. If I can wait for viewDidLoad or viewWillAppear to do any setup, and not write any custom code in init, initWithNibName:bundle: or similar methods I'll know that my object is initialized just like the parent class no mater what way it was instantiated. Frequently I manage to write my classes without any overrides of these init methods.
If I find that I need to put my initialization code in the init methods my strategy is to write just one version of my initialization code, put that in a separate method, and then override all the init methods. The overridden methods call the superclass version of themselves, check for success, then call my internal initialization method.
If these strategies fail, such that it really makes a difference what way an object of this class is instantiated, I'll write custom methods for each of the various init methods.
This is how I solved my own issue:
- (void)loadView
{
if (self.nibName) {
// although docs states "Your custom implementation of this method should not call super.", I am doing it instead of loading from nib manually, because I am too lazy ;-)
[super loadView];
}
else {
self.view = // ... whatever UIView you'd like to create
}
}