I am having a singleton class MyController of type UIViewController. I could able to access the view property like [MyController sharedInstance].view and could able to set that to nil like,
[MyController sharedInstance].view = nil;
I wanna restrict someone accessing view property. How could I stop/restrict that?
I found the solution myself. I overridden the method as
-(void)setView:(UIView*)view {
if (view == nil) {
//ignore - make no change
}
else {
//default performance
[super setView:view];
}
}
Class ProjectSingleton {
static let shared = ProjectSingleton()
//here you can make any variable or function and use it anywhere in project
}
I am using the controller in multiple screens. If the controller is created n number of times, it adds weight to the app. So, to overcome that, I am creating it once and reusing it every where.
You can create a base class for your UIViewController that can be used across multiple views. iOS will handle the memory management correctly if you load a new view. The old view will be unloaded and
- (void)viewDidDisappear:(BOOL)animated
- (void)viewWillDisappear:(BOOL)animated
will be called. There you can dispose of any excess data.
Related
I have this myViewController, that instantiates instances of itself.
Currently, I have a UIButton, that triggers the method
-(void)somethingImportant
However, I want that somethingImportant to happen during the ViewDidLoad, so I don't have to push that button.
But if I put somethingImportant in the ViewDidLoad of myViewController, it is recursively called as many times I have a subview of myViewController.
I tried to put somethingImportant in the application didFinishLaunchingWithOptions: of my app delegate, but somehow that does't work either.
EDIT
So here's the code that might be relevant. I have this UIScrollView with a lot of subviews of myViewController:
- (void)configureScrollView
{
for (int i = 0; i < [self.childViewControllers count]; i++) {
...
myViewController * theSubview =[self.childViewControllers objectAtIndex:i];
....
[theScrollView addSubview:[theSubview view]];
}
}
What is the best approach to make sure that somethingImportant is called only once?
I have this class, that instantiates instances of itself.
This inherently sounds like a bad idea and can easily lead to recursion if you're not careful. Therefore I would suggest you rethink your logic. If you need multiple instances of a class, you should be managing those instances from outside that class, not from within.
However, if you're still insistent on doing this - you can do something similar to what sschale suggests and use a variable to keep track of whether you've called your method or not.
The thing is you'll need to define this variable as static in order for it to be stored at class scope, not instance scope.
For example:
static BOOL firstCalled = NO;
- (void)viewDidLoad {
[super viewDidLoad];
if (!firstCalled) {
firstCalled = YES;
[self foo];
}
}
Each subclass should be calling [super viewDidLoad], on up the chain, so that code really should only be called once.
However, if you need to make sure it executes only once, add #property (nonatomic) BOOL runOnce; to that file's interface, and then in -(viewDidLoad) do:
if(!self.runOnce) {
//all code that is only run once
self.runOnce = YES;
}
I am still relatively new to iOS programming. Here is a question that confused me for a long time.
So in one of the view controllers, before this view controller is pushed into the navigation item, I am passing one parameter, say userId, to it in the prepareForSegue from previous view controller. And when this view controller is loading (initialising) based on the userId from the previous view controller, I am making a network call to fetch a list of information that's related to this user and then populating this information to the model of the current view controller.
Where should I put the logic of this data preparation?
Using viewDidLoad: should be fine for common storyboard use because the storyboard does not reuse view controller. Anyway, for the completeness of my view controller usage scenario, I tend to use this pattern:
Start loading remote data asynchronously in viewWillAppear:
Stop loading remote data in viewWillDisappear:
This make sure that your data will be always updated to the current userId because the ID might be changed after viewDidLoad, e.g. in case of view controller reuse or accessing .view property before setting userId.
You should also track if your data has been loaded. For example, you could make a private boolean field named _isDataLoaded, set it to true when finish loading data and set it to false when cancelling loading data or setting new userId.
To sum it up, the pattern in my idea should be something like this:
#interface UserViewControler : UIViewController {
bool _isDataLoaded;
NSURLConnection _dataConnection;
}
#implementation UserViewController
-(void) setUserId:(int)userId {
if (_userId != userId) {
_userId = userId;
_isDataLoaded = false;
}
}
-(void) viewWillAppear:(BOOL)animated {
if (!_isDataLoaded) {
_dataConnection = // init data connection here
_dataConnection.delegate = self;
[_dataConnection start];
}
}
-(void) viewWillDisappear:(BOOL)animated {
if (_dataConnection) {
[_dataConnection cancel];
_dataConnection = nil;
_isDataLoaded = false;
}
}
// NSURLConnection call this when finish
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_isDataLoaded = true;
_dataConnection = nil;
}
// NSURLConnection call this when fail to load data
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
_isDataLoaded = false;
_dataConnection = nil;
}
It depends on what framework you use to retrieve data from remote server, but the pattern should be like this. This will ensure that:
You will load data only when the view appear.
View controller will not loading more data after disappear.
In case of same userId, data would not be downloaded again.
Support view controller reuse.
- (void) viewDidLoad {
[super viewDidLoad];
// initialize stuff
}
Although, it may be better to do the network call and gather all this information into a custom class that contains all the information, and then perform the segue. Then all you have to do in the new view controller is pulled data out of the object (which would still be done in viewDidLoad).
Arguably, this method might be better because if there's a problem with the network, you can display an error message and then not perform the segue, giving the user an easier way to reattempt the same action, or at least they'll be on the page to reattempt the same action after leaving app to check network settings and coming back.
Of course, you could just segue forward always, and segue backward if there's a network error, but I think this looks sloppier.
Also, it's worth noting that if you're presenting the information with a UICollectionView or a UITableView, the presenting logic can (should) be moved out of viewDidLoad and into the collection/table data source methods.
What I have done in the past is make custom initializers.
+(instancetype)initWithUserID:(NSString)userID;
Here is an example of the implementation.
+(instancetype)initWithUserID:(NSString *)userID {
return [[self alloc] initWithUserID:userID];
}
-(id)initWithUserID:(NSString *)userID {
self = [self initWithNibName:#"TheNameOfTheNib" bundle:nil];
if(self) {
_userID = userID;
}
//do something with _userID here.
//example: start loading content from API
return self;
}
-(void)viewDidLoad {
//or do something with userID here instead.
}
The other thing I would suggest is make a custom class that loads data and uses blocks.
Then you can do something like this
[API loadDataForUserID:userID withCompletionBlock^(NSArray *blockArray) {
//in this case I changed initWithUserID to initWithUsers
[self.navigationController pushViewController:[NextController initWithUsers:blockArray] animated:YES];
}
Since switching to storyboards, I load a view controller via
[self performSegueWithIdentifier:#"identifier" sender:self]
This works perfectly. Now, if I want to set any properties on the destination view controllers, I implement the method prepareForSegue:sender: and set what properties I need to set. Everything works as expected without any problems.
Ever since I starting using this approach over the old
MyViewController *vc = ....
vc.prop = #"value";
[self.navigationController pushViewController:vc];
I've felt that passing parameters to the destination view controller is a little hacky, in particular if the value you're trying to set is not just a static value.
Lets say for example, I have a button which fetches some data from a server. When the data returns, it creates a new object, and then presents a new view controller to display this object. To do this, I call performSegueWithIdentifier:sender:, but that's the end of it. My object is now deallocated and no longer exists, and I have no way of passing it to the prepareForSegue:sender: method, unless I store it in an instance variable.
This feels pretty horrible, as the object isn't meant to last longer than this action, and has no relation to anything else in my current view controller.
In this situation, I understand that I could quite simply request the data in the new view controller but it's just an example.
My question is, is there another way of doing this without it feeling so hacky? Can I get this data into the destination view controller without storing it in an instance variable?
I know I could still use the old approach, but I'd like to stick with the storyboard methods if I can.
Well the sender parameter of the performSegueWithIdentifier:sender is the same one received by the prepareForSegue:sender. So if you want to send a variable to your prepareForSegue:sender the sender is your friend. In your case:
SomeViewController.m
-(void)aMethodThatDownloadsSomeDataFromServer {
NSString *exampleData = [self someDataThatIDownloaded];
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:exampleData];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if(segue.identifier isEqualToString:#"yourSegueIdentifier"]) {
if([sender isKindOfClass:[NSString class]]) { //maybe you want to send different objects
segue.destinationViewController.stringProperty = sender;
}
else {
segue.destinationViewController.objectPorperty = sender;
}
}
}
The accepted solutios is correct but I frequently use another approach when data are shared between more than two segue. I frequently create a singleton class (let's call it APPSession) and I use it as a datamodel, creating and maintaining a session-like structure I can write and read from everywhere in the code.
For complex applications this solution maybe requires too much error prone coding but I've used it succesfully in a lot of different occasions.
APPSession.m
//
// APPSession.m
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import "APPSession.h"
#implementation APPSession
#synthesize myProperty;
static APPSession *instance = nil;
// Get the shared instance and create it if necessary.
+ (APPSession *)instance {
if (instance == nil) {
instance = [[super allocWithZone:NULL] init];
}
return instance;
}
// Private init, it will be called once the first time the singleton is created
- (id)init
{
self = [super init];
if (self) {
// Standard init code goes here
}
return self;
}
// This will never be called since the singleton will survive until the app is finished. We keep it for coherence.
-(void)dealloc
{
}
// Avoid new allocations
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Avoid to create multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
APPSession.h
//
// APPSession.h
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface APPSession : NSObject{
}
#property(nonatomic,retain) NSString* myProperty;
+ (id)sharedInstance;
#end
How to read and write the property myProperty from every part of the app code.
// How to write "MyValue" to myProperty NSString *
[APPSession instance] setMyProperty:#"myValue"]
// How to read myProperty
NSString * myVCNewProperty = [[APPSession instance] myProperty];
With this mechanism I can safely write for instance a value in the APPSession in the first ViewController, perform a segue to a second one, perform another segue to a third one and use the variable written during the first segue.
It's more or less like a SessionScoped JavaBean in Java EE. Please feel free to point out problems in this approach.
All of these answers are correct, but I've found a pretty cool way of doing this. I've tested only in iOS 7 and iOS 8
After declaring and setting the value of the object you wish to pass, in the prepareForSegue method,
[segue.destinationViewController setValue:event forKey:#"property"];
//write your property name instead of "property
I have several popovers in my application and I am having difficulty in determining which popover was dismissed. Is there a "tag" feature equivalent for UIPopOvers?
I can NSLog the popoverController in the popoverContorllerDidDismissPopover method and see the memory reference of each one but that doesn't help.
#pragma mark - Popover controller delegates
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(#"Popover dismised %#", popoverController);
}
An extract from here:
If I understand the question, then basically, no - and it's maddening.
On the one hand you're told that only one popover should be showing at
any one moment. On the other hand you don't automatically get a
reference to that popover. Thus it is up to you to store a reference,
manually, to the current popover controller at the time it shows its
popover, so that you can talk to it later in order to dismiss it.
Popover controller management can thus get really elaborate and
clumsy; you're doing all kinds of work that the system should just be
doing for you.
iOS is funny this way. I'm reminded of how there's no call in iOS 4
that tells you current first responder. Obviously the system knows
what the first responder is, so why won't it tell you? It's kind of
dumb. This is similar; the system clearly knows useful stuff it won't
share with you. m.
There are many ways how to distinguish between popovers. I will list few of them:
You are asking about tag. Note that every popover has a content view controller and this controller has a view that can be tagged. However, using magic integer tags to distinguish between views is arguable in general.
Store the type of the popover into a variable/property in your controller, e.g. as an enum. This is the simplest way.
Add the neccessary information to the popover, but be clever about it, e.g.
#interface MyPopoverController : UIPopoverController
#property (nonatomic, copy, readwrite) void (^dissmissHandler)(void);
#end
#implementation MyPopoverController
- (id)initWithContentViewController:(UIViewController*)contentView {
self = [super initWithContentViewController:contentView];
if (!self) {
return nil;
}
self.delegate = self;
return self;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController*)popover {
assert(popover == self);
if (self.dissmissHandler) {
self.dissmissHandler();
}
}
#end
MyPopoverController* popover = [MyPopoverController alloc] initWithContentViewController:...];
popover.dissmissHandler = ^{
...
};
As #Anoop stated, you can usually only have one popover showing at a time.
One possible solution is to check the contentViewController property on the pop over. If you are storing a reference of each view controller you could do something like:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
if ( popoverController.contentViewController == self.someUIViewController ) {
// do stuff
}
else if ( popoverController.contentViewController == someoTherViewController ) {
//
}
NSLog(#"Popover dismised %#", popoverController);
}
If storing a reference to each content view controller is not possible (or maybe just not a good idea), you could always check its type:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
if ( [popoverController.contentViewController isKindOfClass:[MyAwesomeViewController class]] ) {
// do stuff
}
else if ( [popoverController.contentViewController isKindOfClass:[MyOtherViewController class]] ) {
//
}
NSLog(#"Popover dismised %#", popoverController);
}
Another possible solution, which is probably better from a design stand point of view, would be to pass in a delegate to the view controller contained in the pop over. More here. This way, the view controller displayed can send data back to your main view controller.
I have a viewcontroller that can show several popovers. Not at the same time. Which would be the best way to know which popover is being dismissed at popoverControllerDidDismissPopover?
I have to do different actions regarding the popover that is being dismissed.
Thanks a lot
Something like this should work. (This code is not complete - I assume you know basic memory and class management and other stuff so I focus on the actual problem)
In your class keep some ivars to store reference to the popovercontrollers you created
#interface MyClass : NSObject <UIPopoverControllerDelegate> {
UIPopoverController *popover1;
UIPopoverComtroller *popover2;
}
Init your popovercontrollers as usual and save a referance to each of them in popover1 and popover2.
Then in your implementation of the UIPopoverDelegate protocol:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
if(popoverController == popover1) {
//popover1 was dismissed
} else if (popoverController == popover2) {
//popover2 was dismissed
}
}
EDIT: Looking at your comments, it seems that you mean that you are using only ONE popovercontroller, and replacing it's content view with different UIViewControllers.
If this is the case, I suggest you perform whatever the actions are inside those particular UIViewController in such a way that it affects your applicationĀ“s state.
Then, once the popover is dismissed, you reload your original view with the new refreshed state.
Or you change it to use two different instances of UIPopoverController instead.
This is how in Swift as of Xcode 6.3 beta 3, should be similar in Objective-C.
Your presented view should have a ViewController for itself.
import UIKit
class MenuBookmarksViewController: UITableViewController {
}
Add an extension to the UIViewController class or place the code (below) inside the UIViewController that will be presenting your popovers:
extension UIViewController: UIPopoverPresentationControllerDelegate {
public func popoverPresentationControllerDidDismissPopover(popoverPresentationController: UIPopoverPresentationController) {
if popoverPresentationController.presentedViewController as? MenuBookmarksViewController != nil {
///do your stuff
}
}
}
You are passed which popover is being dismissed in popoverControllerDidDismissPopover:. Use that to determine what you want to do in each case.
You'll probably want to store your UIPopoverController instances as ivars of whatever object is presenting them, and then just compare against the value that you're passed in the delegate method.