recently I started using storyboard and I've the following situation: I want to set the text of an UILabel from the AppDelegate. So I created an instance of my ViewController
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle: nil];
ViewController *controller = (ViewController*)[mainStoryboard
instantiateViewControllerWithIdentifier: #"mainViewController"];
myViewController = controller;
[window addSubview:myViewController.view];
[window makeKeyAndVisible];
and called the following method from the delegate
- (void) updateParameterLabel:(NSString *)parameter {
NSLog(#"URL-2: %#", parameter);
parameterLabel.text = parameter;
}
But the parameter is not shown in the UI.
Another think, which is kind of strage:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(#"View did Appear");
}
The "View did appear" is logged twice ...
Any hints?
Regards,
Sascha
Setting the text of a UILabel from your application delegate isn't great design. Your view controllers should be managing the content of your views, hence their name. Typically your storyboard is instantiated automatically, and you don't need any of the storyboardWithName et code you've got, assuming you're working with Apple's default templates.
Maybe think about re-architecting your application to follow the 'model-view-controller' pattern more strictly, and also look at how Apple instantiate storyboards automatically (just create a new storyboard project in XCode to see this).
If you still want to make it work, make the UILabel a property of your viewcontroller and set the label by using
In delegate :
- (void) updateParameterLabel:(NSString *)parameter {
NSLog(#"URL-2: %#", parameter);
[myViewController updateParemeter:parameter];
}
In myViewController:
- (void) updateParameterLabel:(NSString *)parameter {
NSLog(#"URL-2: %#", parameter);
parameterLabel.text = parameter;
[self.view setNeedsDisplay];//edit
}
So use the viewController to update your label. Of course you need the label as a property in your viewController
For what I see you are trying to update the label before it appears, so why don't you try calling your updateLabel method in the viewWillAppear, it would be something like this
-(void)viewWillAppear:(BOOL)animated{
[self updateParameterLabel:#"Some Text"];
[super viewWillAppear:YES];
}
And updateParameterLabel has to be implemented in the viewController.
Related
Situation:
3 view controllers, white is main, red and blue are embedded (container view controllers)
I select something in red,it switches to blue,I swipe the blue - it goes back and I want to change text in the label to some custom text
Sequence:
So I use delegation and it also works.But when I try to set label's text property(in the method that confirms my protocol) via self.thisLabel.text NSLog says this label is nil, although I have an outlet. If I use something like [self.view viewWithTag:tag] NSLog shows that label is there but I can't set the text, it stays the same.
code in white:
header
#interface ContactsViewController : UIViewController <BlueViewControllerDelegate>
implementation
- (void)adjustLabel:(NSString *)string{
NSLog(#"i am here baby %#",self.thisLabel);
[[self.view viewWithTag:57] setValue:string forKey:#"text"];
}
code in blue:
header:
#protocol BlueViewControllerDelegate <NSObject>
-(void)adjustLabel:(NSString*)string;
#end
and:
#property id<ViewControllerDelegate> delegate;
implementation
- (void)viewDidLoad{
[super viewDidLoad];
UIStoryboard *sB = [UIStoryboard storyboardWithName:#"Main" bundle:[NSBundle mainBundle]];
WhiteViewController *WhiteVc = [sB instantiateViewControllerWithIdentifier:#"whiteID"];
self.delegate = WhiteVc;
}
-(void)willMoveToParentViewController:(UIViewController *)parent{
[self.delegate adjustLabel:#"some custom text"];
}
-(IBAction)swipePerformed:(UISwipeGestureRecognizer*)sender{
[self willMoveToParentViewController:nil];
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
Any thoughts?
If I understand your VC hierarchy correctly, the WhiteViewController is the parent of BlueViewController, so you shouldn't instantiate a new one from the storyboard in viewDidLoad but do instead:
WhiteViewController *whiteVc = (WhiteViewController*)self.parentViewController;
self.delegate = whiteVC;
Also, I don't find overriding willMoveToParentViewController particularly useful. You could put your delegate call to your swipe action.
To start I am building an app to learn the basics of Objective-C. If there is anything unclear please let me know and I will edit my question.
The app is supposed to have the next functionality.
Open the camera preview when the app is executed. On the top there is a button to go to a TemplateController where the user can select an array of frames to select from a UICollectionView. User selects the Template and returns to the Camera Preview. User takes a picture and the picture with the frame selected is shown in the PreviewController. If the user doesn't like the frame and wants to switch it for another one. PreviewController has button on top to go to the TemplateController, select the frame and go back again to the PreviewController with the new frame.
I do not want to create an object for the frame everytime. I want the AppDelegate to hold that object. To keep it alive per say?(sorry, English is not my mother tongue).
I was thinking to use NSUserDefaults BUT I really want to do it using the AppDelegate. So at this point NSUserDefaults is not an option.
Now, I am using storyboards with a navigation controller. A screenshot is available here
Right now when I pass from the TemplateController to my PreviewController my code looks like this:
Reaching TemplateController from MainController or PreviewController
- (IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
Passing the data from TemplateController to its controller's destiny (Either MainController or PreviewController)
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
_selectedLabelStr = [self.frameImages[indexPath.section] objectAtIndex:indexPath.row];
[self.collectionView deselectItemAtIndexPath:indexPath animated:NO];
[self dismissViewControllerAnimated:YES completion:^{
if ([self.frameDelegate respondsToSelector:#selector(templateControllerLoadFrame:)])
{
[self.frameDelegate performSelector:#selector(templateControllerLoadFrame:) withObject:self];
}
}];
}
This loads the selected frame in PreviewController
- (void)templateControllerLoadFrame:(TemplateController *)sender
{
UIImage *tmp = [UIImage imageNamed:sender.selectedLabelStr];
_frameImageView.image = tmp;
}
My problem is, I don't have very clear what changes I have to do on the AppDelegate(it is untouched right now). What would be the best approach to accomplish this?
Main issue is when Tamplate is chosen before taking the still image. If I select the frame after taking the picture then it displays.
I am not certain that I understand your question. Stuffing an object into the app delegate solution may not be the best way forward. In fact I believe you ought to look at the delegation pattern that is used by Apple to communicate between view controllers. Please note that you appear to be doing half of the delegate pattern already. For example you make your PreviewController a frameDelegate of the TemplateController.
So I would think you'd have something like the following to transfer information from TemplateController back to the PreviewController. Note that I've included prepare for segue as that is a common pattern to push a data object forward (it will be called if you connect a segue from the PreviewController to the TemplateController and in your action method call performSegueWithIdentifier:#"SegueTitle"). Use of the "templateControllerDidFinish" delegation method is a common pattern used to push information back from TemplateController when it closes.
TemplateController.h
#class TemplateController;
#protocol TemplateControllerDelegate <NSObject>
-(void) templateControllerDidFinish :(TemplateController*)controller;
#end
#interface TemplateController : UIViewController
#property (nonatomic, weak) id <TemplateControllerDelegate>delegate;
...
#end
TemplateController.m
//! The internals for this method can also be called from wherever in your code you need to dismiss the TemplateController by copying the internal
-(IBAction)doneButtonAction:(id)sender
{
__weak TemplateController*weakSelf = self;
[self dismissViewControllerAnimated:YES completion:^{
[self.delegate templateControllerDidFinish:weakSelf];
}];
}
PreviewController.h
#import "TemplateController.h"
#interface PreviewController<TemplateControllerDelegate>
...
#end
PreviewController.m
#implementation
...
-(void) templateControllerDidFinish :(TemplateController*)controller
{
self.dataProperty = controller.someImportantData;
...
}
...
-(void)prepareForSegue:(UIStoryboardSegue*)segue sender:(id)sender
{
if ( [[segue identifier]isEqualToString:#""] )
{
TemplateController *tc = [segue destinationViewController];
tc.delegate = self;
tc.data = [someDataObjectFromPreviewController];
}
}
To fix this situation a bit more:
Add a segue from the PreviewController to the TemplateController
(Ctrl-drag from Preview view controller to the Template Controller
in the document outline mode)
Name the segue identifier in the identity inspector
Change your code that presents the view controller from:
(IBAction)showFrameSelector:(id)sender
{
UIStoryboard *storyboard;
storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
TemplateController *templateController = [storyboard instantiateViewControllerWithIdentifier:#"TemplateController"];
templateController.frameDelegate = self;
[self presentViewController:templateController animated:YES completion:nil];
}
to
- (IBAction)showFrameSelector:(id)sender
{
[self performSegueWithIdentifier:#"SegueTitle"];
}
Add your data object to the target view controller as noted in prepareForSegue and you will be in good shape. Then use the delegate method to catch any data returned from your template (just add the data as properties to the controller and you should be golden)
You can see a better example of this delegation in a utility project template from Xcode (I just keyed this in..) I hope this information helps. You can get more information at these resources and also by searching Google and SO for iOS delegation :
Concepts in Objective C (Delegates and Data Sources)
Cocoa Core Competencies
I was trying to learn UIPageViewControllers and hit an Issue which I couldn't resolve.
This is what I tried to do:
Steps:
I simply created 2 view controllers and a page view controller in
StoryBoard.
Then I added some code to the File's Owner of PageViewController to
behave as a dataSource and delegate to itself.
When I ran, things worked well.
I added some buttons, and text fields to the second view controller.
I ran, worked well.
Now I added a text view to the second view controller and ran. When I tried to write something inside the text view, the page control jittered and moved to first view controller.
Has anyone experience this ever?
#interface AMPageViewController : UIPageViewController <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
#end
The implementation:
#import "AMPageViewController.h"
#interface AMPageViewController ()
{
UIViewController *mainController;
UIViewController* socController;
}
#end
#implementation AMPageViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard"
bundle: nil];
mainController = (UIViewController*)[mainStoryboard instantiateViewControllerWithIdentifier: #"First"];
socController = (UIViewController*)[mainStoryboard instantiateViewControllerWithIdentifier: #"Second"];
[self setViewControllers:#[mainController]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:nil];
self.dataSource = self;
self.delegate = self;
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
if (viewController == socController )
return mainController;
else return nil;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
if (viewController == mainController )
return socController;
else return nil;
}
- (NSInteger)presentationCountForPageViewController:(UIPageViewController *)pageViewController
{
return 2;
}
- (NSInteger)presentationIndexForPageViewController:(UIPageViewController *)pageViewController
{
return 0;
}
#end
If you want to download and try the project
I've investigated a lot on this problem.
It seems a bug related to the internal (private) UIScrollView of the UIPageViewController.
If you search on StackOverflow you will find a lot of post with this problem and no solutions...
I seems that the UITextView (which is an UIScrollView and, AFAIR, has an internal UIWebView), sends some strange message to it's superviews chain, that makes the private UIScrollView of the UIPageViewController scrolling to the top-left corner.
I would have tried to block this message using method swizzling, but this is probably not ok for AppStore. So I tried other things.
The final solution is very simple: simply, embed your UITextView inside an UIScrollView!
This is a link to your project updated
If you do so, you'll solve the problem!
Try and let me know
EDIT:
How did I arrive to this solution:
An intuition.
A lot of debug and stack traces had make me think that the problem was related to a bug in the "nesting UIScrollView" system and some messages sent from the inner view to its superview.
UITextView inherits from UIScrollView and has inside an UIWebDocumentView (private) which is another UIScrollView. During the debug I saw a lot of messages (private methods) like "relayout superview" sent to the upper UIScrollView's. So, for some reason, the inner scroll view (UIWebDocumentView?) was sending a message/event to it's superview. This message/event (probably because of a bug) was not stopping to the external UITextView, and was forwarded to the UIScrollView handled by UIPageViewController.
Embedding the UITextView inside a simple UIView was not enough, because UIView forward the message to it's superview if it can't handle.
I thought: UIScrollView probably doesn't (otherwise it wouldn't simple to nest UIScrollViews), so I tried and it worked.
This is all a supposition because I stopped inspecting, I will have a more in-depth look this week.
Build target iOS-7.0.
The scrollview trick wasn't working for me. Tried to embed the textview in a scrollview through storyboard and code but no luck.
Simply delaying the call to the textview did it. Not very elegant, but its the only thing I've gotten to work so far.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.textView becomeFirstResponder];
});
}
Tested, working on my iPhone 5 and my ultra-slow iPhone4. Although its totally possible that whatever implementation detail enables the textview to become the responder could take longer than the set time. So keep in mind this isn't exactly bulletproof.
--EDIT--
Well... it's working on my iPhone 4 beater with a delay of 0.0000000000000001
you did not set before and after view controllers and also look in to first responder for socController
I use the method performSegueWithIdentifier:sender: to open a new ViewController from a storyboard-file programmatically. This works like a charm.
But on every time when this method is being called, a new ViewController would be created. Is it possible to use the existing ViewController, if it exista? I don't find anything about this issue (apple-doc, Stack Overflow, ...).
The Problem is:
On the created ViewController the user set some form-Elements and if the ViewController would be called again, the form-elements has the initial settings :(
Any help would be appreciated.
Edit:
I appreciate the many responses. Meanwhile, I'm not familiar with the project and can not check your answers.
Use shouldPerforSegueWithIdentifier to either allow the segue to perform or to cancel the segue and manually add your ViewController. Retain a pointer in the prepareForSegue.
... header
#property (strong, nonatomic) MyViewController *myVC;
... implementation
-(BOOL) shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender{
if([identifier isEqualToString:#"MySegueIdentifier"]){
if(self.myVC){
// push on the viewController
[self.navigationController pushViewController:self.myVC animated:YES];
// cancel segue
return NO;
}
}
// allow the segue to perform
return YES;
}
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"MySegueIdentifier"]){
// this will only be called the first time the segue fires for this identifier
// retian a pointer to the view controller
self.myVC = segue.destinationViewController;
}
}
To reuse an existing UIViewController instance with a segue create the segue from scratch and provide your own (existing) destination (UIViewController). Do not forget to call prepareForSegue: if needed.
For example:
UIStoryboardSegue* aSegue = [[UIStoryboardSegue alloc] initWithIdentifier:#"yourSegueIdentifier" source:self destination:self.existingViewController]
[self prepareForSegue:aSegue sender:self];
[aSegue perform];
Following code makes singleton view controller.
Add them to your destination view controller implementation, then segue will reuse the same vc.
static id s_singleton = nil;
+ (id) alloc {
if(s_singleton != nil)
return s_singleton;
return [super alloc];
}
- (id) initWithCoder:(NSCoder *)aDecoder {
if(s_singleton != nil)
return s_singleton;
self = [super initWithCoder:aDecoder];
if(self) {
s_singleton = self;
}
return self;
}
I faced this problem today and what I have done is to create the view controller manually and store it's reference.
Then every time I need the controller, check first if exists.
Something like this:
MyController *controller = [storedControllers valueForKey:#"controllerName"];
if (!controller)
{
controller = [[UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:NULL] instantiateViewControllerWithIdentifier:#"MyControllerIdentifierOnTheStoryboard"];
[storedControllers setValue:controller forKey:#"controllerName"];
}
[self.navigationController pushViewController:controller animated:YES];
Hope it helps.
Create a property for the controller.
#property (nonatomic, weak) MyController controller;
And use some kind of lazy initialization in performSegueWithIdentifier:sender
if (self.controller == nil)
{
self.controller = [MyController alloc] init]
...
}
In this case, if controller was already created, it will be reused.
Firstly you would be going against Apple's design in Using Segues: "A segue always presents a new view controller".
To understand why it might help to know that what a segue does is create a new view controller and then the perform calls either showViewController or showDetailViewController depending on what kind of segue it is. So if you have an existing view controller just call those methods! e.g.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
Event *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
self.detailViewController.detailItem = object;
[self showDetailViewController:self.detailViewController.navigationController sender:self];
}
You would need to make the Viewcontroller into a singleton class.
I've got two classes. ManagingViewController, a subclass of NSViewController, and ViewController, a subclass auf ManagingViewController. In Viewcontroller I've got a NSTextField which I want to become the firstResponder, but I didn't manage that.
So it is nearly the same like the Chapter 29 in Hillegass' book Cocoa Programming for Mac OS X (Download of the book's examples) except of an NSTextField which is set to firstResponder.
Can anybody point me to the correct way?
You need to set the text field as the first responder by using -[NSWindow makeFirstResponder:].
Since this is an NSWindow method, it only makes sense after you’ve added the corresponding view to the window, i.e., after you’ve added the view as a subview inside the window view hierarchy. In the book’s example, this happens when you set the view as the content view of the box inside the window. For example:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
if ([vc class] == [ViewController class]) {
[w makeFirstResponder:[(ViewController *)vc myTextField]];
}
}
This assumes ViewController exposes a getter method called -myTextField.
You can make this more generic by having your view controllers expose a method that returns the object that the view controller recommends as the first responder. Something like:
#interface ManagingViewController : NSViewController
…
- (NSResponder *)recommendedFirstResponder;
#end
#implementation ManagingViewController
…
- (NSResponder *)recommendedFirstResponder { return nil; }
#end
And, in your concrete subclasses of ManagingViewController, have -recommendedFirstResponder return the object that should be the window’s first responder:
#implementation ViewController
…
- (NSResponder *)recommendedFirstResponder { return myTextField; }
#end
Having done that, you can change your -displayViewController: to something like:
- (void)displayViewController:(ManagingViewController *vc) {
// Try to end editing
NSWindow *w = [box window];
…
// Put the view in the box
NSView *v = [vc view];
[box setContentView:v];
// Set the first responder
NSResponder *recommendedResponder = [vc recommendedFirstResponder];
if (recommendedResponder) [w makeFirstResponder:recommendedResponder];
}
Have you tried [[myTextField window] makeFirstResponder:myTextField]; ?
simple. Goto you xib file in interface builder. right click the first responder field. it will show the connection , remove the connection and connect it to the desired responder. let me know if this works