I want to check the pasteboard and show an alert if it contains specific values when the view appears. I can place the code into viewDidLoad to ensure it's only invoked once, but the problem is that the alert view shows too quickly. I know I can set a timer to defer the alert's appearance, but it's not a good work-around I think.
I checked the question iOS 7 - Difference between viewDidLoad and viewDidAppear and found that there is one step for checking whether the view exists. So I wonder if there's any api for doing this?
Update: The "only once" means the lifetime of the view controller instance.
There is a standard, built-in method you can use for this.
Objective-C:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self isBeingPresented] || [self isMovingToParentViewController]) {
// Perform an action that will only be done once
}
}
Swift 3:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.isBeingPresented || self.isMovingToParentViewController {
// Perform an action that will only be done once
}
}
The call to isBeingPresented is true when a view controller is first being shown as a result of being shown modally. isMovingToParentViewController is true when a view controller is first being pushed onto the navigation stack. One of the two will be true the first time the view controller appears.
No need to deal with BOOL ivars or any other trick to track the first call.
rmaddy's answers is really good but it does not solve the problem when the view controller is the root view controller of a navigation controller and all other containers that do not pass these flags to its child view controller.
So such situations i find best to use a flag and consume it later on.
#interface SomeViewController()
{
BOOL isfirstAppeareanceExecutionDone;
}
#end
#implementation SomeViewController
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if(isfirstAppeareanceExecutionDone == NO) {
// Do your stuff
isfirstAppeareanceExecutionDone = YES;
}
}
#end
If I understand your question correctly, you can simply set a BOOL variable to recognize that viewDidAppear has already been called, ex:
- (void)viewDidAppear {
if (!self.viewHasBeenSet) { // <-- BOOL default value equals NO
// Perform whatever code you'd like to perform
// the first time viewDidAppear is called
self.viewHasBeenSet = YES;
}
}
This solution will call viewDidAppear only once throughout the life cycle of the app even if you create the multiple object of the view controller this won't be called after one time. Please refer to the rmaddy's answer above
You can either perform selector in viewDidLoad or you can use dispatch_once_t in you viewDidAppear. If you find a better solution then please do share with me. This is how I do the stuff.
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:#selector(myMethod) withObject:nil afterDelay:2.0];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
static dispatch_once_t once;
dispatch_once(&once, ^{
//your stuff
[self myMethod];
});
}
By reading other comments (and based on #rmaddy 's answer), I know this is not what OP asked for, but for those who come here because of title of the question:
extension UIViewController {
var isPresentingForFirstTime: Bool {
return isBeingPresented() || isMovingToParentViewController()
}
}
UPDATE
You should use this method in viewDidAppear and viewWillAppear. (thanks to #rmaddy)
UPDATE 2
This method only works with modally presented view controllers and pushed view controllers. it's not working with a childViewController. using didMoveToParentViewController would be better with childViewControllers.
You shouldn't have issues in nested view controllers with this check
extension UIViewController {
var isPresentingForFirstTime: Bool {
if let parent = parent {
return parent.isPresentingForFirstTime
}
return isBeingPresented || isMovingFromParent
}
}
Try to set a BOOL value, when the situation happens call it.
#interface AViewController : UIViewController
#property(nonatomic) BOOL doSomeStuff;
#end
#implementation AViewController
- (void) viewWillAppear:(BOOL)animated
{
if(doSomeStuff)
{
[self doSomeStuff];
doSomeStuff = NO;
}
}
in somewhere you init AViewController instance:
AddEventViewController *ad = [AddEventViewController new];
ad.doSomeStuff = YES;
Not sure why you do this in ViewDidAppear? But if you want doSomeStuff is private and soSomeStuff was called only once, here is another solution by notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomeStuff) name:#"do_some_stuff" object:nil];
- (void) doSomeStuff
{}
Then post when somewhere:
[[NSNotificationCenter defaultCenter] postNotificationName:#"do_some_stuff" object:nil];
swift 5
I've tried isBeingPresented() or isMovingToParent.
But It doesn't work.
So I tried below code. and It's work for me!
override func viewDidAppear(_ animated: Bool) {
if (self.isViewLoaded) {
// run only once
}
}
You can use this function in ViewDidLoad method
performSelector:withObject:afterDelay:
it will call that function after delay. so you don't have to use any custom timer object.
and For once you can use
dispatch_once DCD block.Just performSelector in the dispatch_once block it will call performSelector only once when ViewDidLoad is called
Hope it helps
Related
I have implemented AdMob & everything seems to work,
But i wonderd, how can i put the banner in all of my view controllers?
For now, i have the banner only on the RootViewController.
I have total of 4 view controllers.
Thanks.
What you want here is a GADBannerView singleton of sorts. You can probably create a wrapping class to act as a singleton for your adView, so something like:
#interface GADMasterViewController : UIViewController {
GADBannerView *adBanner_;
BOOL didCloseWebsiteView_;
BOOL isLoaded_;
id currentDelegate_;
}
And just make sure that GADMasterViewController always returns a singleton:
+(GADMasterViewController *)singleton {
static dispatch_once_t pred;
static GADMasterViewController *shared;
// Will only be run once, the first time this is called
dispatch_once(&pred, ^{
shared = [[GADMasterViewController alloc] init];
});
return shared;
}
Have a method which resets the current view controller that's holding on to the adView:
-(void)resetAdView:(UIViewController *)rootViewController {
// Always keep track of currentDelegate for notification forwarding
currentDelegate_ = rootViewController;
// Ad already requested, simply add it into the view
if (isLoaded_) {
[rootViewController.view addSubview:adBanner_];
} else {
adBanner_.delegate = self;
adBanner_.rootViewController = rootViewController;
adBanner_.adUnitID = kSampleAdUnitID;
GADRequest *request = [GADRequest request];
[adBanner_ loadRequest:request];
[rootViewController.view addSubview:adBanner_];
isLoaded_ = YES;
}
}
Then displaying your ad is just a matter of:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
shared = [GADMasterViewController singleton];
[shared resetAdView:self];
}
You probably need to set up a delegate to forward notifications as well since the AdMob SDK doesn't act well to delegates changing on it in the middle of a request.
You can find a blog post about this here.
I don't know how adMob works but like everything else you can create a BaseViewController in which you can add your adMob(in the viewDidLoad method) and then all the other viewControllers can subClass this BaseViewController. just call [super viewDidLoad]; in the viewDidLoad methods of your viewControllers and you will have it...
hoping this sorts your problem... :)
I am trying to update a UILabel in a parent View after someone makes a change in a modal view. So, after they click "save" ... the newly entered value would change what text is displayed on the parent view controller.
But, I can't seem to get that UILabel to refresh the newly entered value.
Any ideas on what I can try? I've tried a few things, but being the view is already loaded, nothing is getting "refreshed".
Thanks!
There are many ways to do this. One way is to use NSNotificationCenter to be able to do calls between different classes. So in the parent view you will have a function responsible for the update (lets call it updateLabel) and you will do the following:
- (void) updateLabel
{
yourLabel.text = #"what you need";
}
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateLabel) name:#"DoUpdateLabel" object:nil];
}
Now in other view simply post a notification in the save button:
[[NSNotificationCenter defaultCenter] postNotificationName:#"DoUpdateLabel" object:nil userInfo:nil];
EDIT:
I have to mention 2 things here:
In this scenario it is always preferable to have Shared Data Modal where you save your data in so you can access this data in any view in your program. In other words it is a good practice to separate the data from classes.
Remember to resomve the NSNotificationCenter that you used in the main view by adding [[NSNotificationCenter defaultCenter] removeObserver:self];
To elaborate on my comment. This is how I would implement a delegation method to update the label.
In the header of the parent view controller:
#import "ModalViewController.h"
#interface ViewController : UIViewController <ModalViewControllerDelegate>
/* This presents the modal view controller */
- (IBAction)buttonModalPressed:(id)sender;
#end
And in the implementation:
/* Modal view controller did save */
- (void)modalViewControllerDidSave:(ModalViewController *)viewController withText:(NSString *)text
{
NSLog(#"Update label: %#", text);
}
/* Prepare for segue */
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"modalSegue"])
{
ModalViewController *mvc = (ModalViewController *) segue.destinationViewController;
mvc.delegate = self;
}
}
/* Present modal view */
- (IBAction)buttonModalPressed:(id)sender
{
[self performSegueWithIdentifier:#"modalSegue" sender:self];
}
Here you see the delegation method in the top.
The header of the modal view controller would contain the delegation protocol like this:
#protocol ModalViewControllerDelegate;
#interface ModalViewController : UIViewController
#property (nonatomic, weak) id <ModalViewControllerDelegate> delegate;
- (IBAction)buttonSavePressed:(id)sender;
#end
#protocol ModalViewControllerDelegate <NSObject>
- (void)modalViewControllerDidSave:(ModalViewController *)viewController withText:(NSString *)text;
#end
The implementation of the modal view controller would contain a method similar to this one:
/* Save button was pressed */
- (IBAction)buttonSavePressed:(id)sender
{
if ([self.delegate respondsToSelector:#selector(modalViewControllerDidSave:withText:)])
[self.delegate modalViewControllerDidSave:self withText:#"Some text"];
[self dismissModalViewControllerAnimated:YES];
}
When the save button is pressed, the delegate is notified and the text in your text view is sent through the delegation method.
in SWIFT:
ParentViewController :
func updateLabel() {
yourLabel.text! = "what you need"
}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.updateLabel), name: "DoUpdateLabel", object: nil)
}
In OtherView:
#IBAction func closePopUp(sender: AnyObject) {
NSNotificationCenter.defaultCenter().postNotificationName("DoUpdateLabel", object: nil, userInfo: nil)
}
Good morning,
I've creates a Subclass of UIScrollView and I now want to know when the user is scrolling in my subclass. For that I implemented it like the following:
ImageScroller.h
#interface UIImageScroller : UIScrollView <UIScrollViewDelegate> {
ImageScroller.m (within the #implementation)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"did scroll");
}
The problem is, that the method scrollViewDidScroll doesn't seem to get fired.
Is there any possibility to get it to work?
I also tried to set the delegate to it self, but it doesn't work.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
self.directionalLockEnabled = YES;
[self setDelegate:self];
}
return self;
}
I added a ScrollView to my XIB-File and set the Class if it to my ImageScroller. Also I've set the Fileowner and I'm using the UIScrollViewDelegate in the .h-File of the ViewController as well as implementing the Method scrollViewDidScroll in the .m-file.
When I set the delegate of my ImageScroller in the code of the .m-file from the XIB like
[imageScroller setDelegate:imageScroller]
the scrollViewDidScroll is fired in my ImageScroller-Subclass, but the one in my ViewController isn't fired, but I need both.
Any solutions for that?
Thanks for your answers in advance.
I ran into the same problem creating a subclass of the UIScrollView but solved it this way. As mentioned above, you can set yourself (subclass instance) as the delegate however this is what I did.
In the sub class I overrode the following methods
//Override this to detect when the user is scrolling, this is also triggered during view
//layout. Here you can check your deceleration rate and determine when the scrolling has
//grinded to a halt.
-(void)setContentOffset:(CGPoint)contentOffset
//Override this to detect when the view has been resized (just a handy override)
-(void)setFrame:(CGRect)frame;
As far as the other UIScrollViewDelegate methods, the View Controller should be responsible for handling those in my opinion.
I think you can try setting self.delegate = self; to receive events.
If anyone is looking for a Swift answer, it's as simple as this:
override var contentOffset: CGPoint {
didSet {
if contentOffset != oldValue {
//same as scrollViewDidScroll
}
}
}
Ok, I'm having a lot of problems right now trying to get initWithCoder: to work right. I have a nib file that gets loaded, and in my app delegate, I call unarchiveWithFile: for the view controller that is associated with that nib, and now my app crashes. I can see that initWithCoder: is being called twice, presumably once from when awakeFromNib: is called, and then from when I call unarchiveWithFile: since the view controller conforms to NSCoding. But now either it crashes as soon as the view loads or when I press an IBOutlet. Any suggestions??
Edit: Here's the code for initWithCoder:
- (id)initWithCoder:(NSCoder *)coder {
[super initWithCoder:coder];
[[self mapView] addAnnotations:[[coder decodeObjectForKey:#"Annotations"] retain]];
return self;
}
All I'm doing is decoding an array of annotations for a map view, but somewhere along the line this method is being called twice and then it crashes.
Don't forget to put the nil check in your init methods. E.g. the method you posted would be more correct if you wrote it as:
- (id)initWithCoder:(NSCoder *)coder {
if (self = [super initWithCoder:coder]) {
[[self mapView] addAnnotations:[[coder decodeObjectForKey:#"Annotations"] retain]];
}
return self;
}
That's not the cause of your problem, however.
Is there are good reason for you unarchiving your view controller yourself? If you're not doing anything special, you can rely on the existing mechanisms to do it. The default implementation of init for a UIViewController looks for a nib with the same name as your view controller, and if it exists, it loads the nib (via initWithNibName).
If there is data which you need to archive in and out, it may be that it shouldn't be actually part of the UIViewController. Factor it out elsewhere perhaps?
you can try
- (id)initWithCoder:(NSCoder *)coder {
if(self == nil)
{
[super initWithCoder:coder];
[[self mapView] addAnnotations:[[coder decodeObjectForKey:#"Annotations"] retain]];
}
return self;
}
so basically in my app delegate i have a navigation.controller
This navigation controller has a view of a class named MainScreen.
In MainScreen.m , i have a IBAction which will bring me to a SelectionScreen.m page by pushing it. here is the coding for it
SelectionScreen *aSelectionScreenViewController = [[SelectionScreen alloc]initWithNibName:#"SelectionScreen" bundle:nil];
[self.navigationController pushViewController:aSelectionScreenViewController animated:YES];
[aSelectionScreenViewController release];
So how do i check if my current navigationController.view = this selectionscreen.view?
The reason for checking which current view it is, is because when i receieve a push notification, i would want to automatically switch to this SelectionScreen.m page and invoke some methods within it. But this checking can only be done in the appDelegate because the didReceiveRemoteNotification method is located in there.
This is how i'm doing it
for example if you have three ViewControllers ,and any of those have possibility to be pushed by NavigationController:
ViewControllerA
ViewControllerB
ViewControllerC
Then what you need to do is:
-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
if ([[self.navigationController topViewController] isKindOfClass:[ViewControllerA class]]) {
//do sth
}
if ([[self.navigationController topViewController] isKindOfClass:[ViewControllerB class]]) {
//do sth
}
if ([[self.navigationController topViewController] isKindOfClass:[ViewControllerC class]]) {
//do sth
}
}//end of code
One way is to save selectionScreenViewController as a property of your app delegate, then:
if (self.navigationController.topViewController == self.selectionScreenViewController) {
//...
}
else {
//...
}
Hey guys, i did it in a simple way. In every view controller i had, i removed all objects and assigned an object to an array in the appdelegate. So this way, everytime i go to a new view, the value is different.
So in appdidrecieveremotenotification, i can check that array and decide on what to do accordingly.
Its just a simple way of checking.