How can I simply store URL data between UIViews? - iphone

I need to transfer NSURL data to target UIViewController for UIWebView. How can I do it to load required url from ViewDidLoad of target view? I am using storyboard, ARC, iOS5.

You could use a delegate to send the URL from UIViewController to UIWebView.
In your webview create a delegate property:
#property(nonatomic,weak) (id) delegate;
Synthezize as normal. Then in the webviews viewDidLoad method write:
self.someWebViewURLProperty = self.delegate.someURLPropertyFromUIController;
Now set the delegate in your ViewController. In your UIViewController in the viewDidLoad method write:
self.webViewProperty.delegate = self;
It may seem a bit more involved than using prepareForSegue, but both methods are valid. I prefer delegation as its a standard cocoa design pattern.

Related

Reference the class that a subview was added from

I'm adding a subview to my primary iPad UIViewController, and within that subview I need to reference said view controller in order to play a video using that controller.
Can anyone help me out with the way that should be done, and possibly a code example?
Thank you in advance.
Regards,
Ed
EDIT (A LITTLE BIT MORE INFO):
This subview is a view from a uiviewcontroller class that is designed for the iPhone. It's a table that loads a video when a row is pressed. The movieplayer loads the video within the referenced viewcontroller (which is why I want to reference the iPad view controller from within the subview). The view is basically used within an iPad app in it's same form.
This sounds like an architecture problem. It's not up to your view to tell something to play a sound. That's a controller's job. It's up to your view to tell "someone" that it was touched, or slid, or whatever the user has done. "Someone" (who is watching) will then perform the correct response to that.
To do this, your view should generally take a target, and possibly and action. Look at how UIControl (for example, UIButton) informs other objects that it has been activated. The observer (controller) then reacts accordingly.
EDIT
The view should not load a video. A view controller should load the video and install it into the correct view. The only job the view has is to tell its view controller that it has been pressed. UITableView handles this automatically with the UITableViewDelegate method tableView:didSelectRowAtIndexPath:. If you're not using a UITableView, you should still follow this pattern. The view accepts has a delegate and tells the delegate (controller) that something was selected. Then the controller updates the views with the new data.
You probably already have the main view living in a property of your application delegate (it is commonly assigned via the application's .xib file, look in the app delegate's applicationDidFinishLaunching: method, where it adds the main view as a subview to the window, something like:
[window addSubview:primaryController.view];
[window makeKeyAndVisible];
).
So anywhere in your app where you need to access the main view, you can do:
[(MyAppDelegateType*)([UIApplication sharedApplication].delegate).primaryController somePrimaryControllerMethod];
Edit: while this will work, I agree with Rob Napier that this isn't the best way to do it, architecture-wise.
You could just use [yourSubview superview] to get the superview.
Can't you just add a property to the view like this:
#property (nonatomic, assign) UIViewController *parentViewController;
And on initialization of the view (from within the UIViewController), set the property:
UIView *customView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 460.0f)];
customView.parentViewController = self;
And from then on you should be able to call the parentViewController from within the view.
Edit: Rob Napier has a good point, it's probably a better idea to set a target and selector from within the view instead of a view controller. This way you should be able to hook up methods directly with the view. The properties would look like this:
#property (nonatomic assign) id target;
#property (nonatmic, assign) SEL selector;
And perhaps add a designated initializer to your view:
- (id)initWithFrame:(CGRect)aFrame target:(id)aTarget selector:(SEL)aSelector
{
self = [super initWithFrame:aFrame];
if (self)
{
self.target = aTarget;
self.selector = aSelector;
}
return self;
}

Calling method in current view controller from App Delegate in iOS

I have two view controllers (BuildingsViewController and RoomsViewController) that both use a function within the App Delegate called upload. The upload function basically does an HTTP request, and if its successful or unsuccessful, triggers a uialertview. This is working fine.
The part I'm struggling with is from within the app delegate's connectionDidFinishLoading method. I need to be able to basically refresh the current view controller via perhaps viewWillAppear method of that view controller. Inside the viewWillAppear function of each view controller I have code which determines the buttons on the bottom toolbar.
I want the "upload" button in the toolbar of each view controller to automatically be removed when the uploading is done via the app delegate.
I've tried doing [viewController viewWillAppear:YES] from within the connectionDidFinishLoading method of the app delegate, but it never gets called.
I hope I'm clear enough. Any help is greatly appreciated.
Thanks.
To do the refresh of the view do not call viewWillAppear if the view is already displayed. What you want to do is the following:
When ConnectionDidFinishLoading method is triggered post a notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"refreshView" object:nil];
In your viewController observe for this notification. You do it by adding this code to your init or viewDidLoad method
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshView:) name:#"refreshView" object:nil];
Now implement -(void)refreshView:(NSNotification *) notification method in your viewController to manage your view to your liking.
If you are targeting iOS 4.0 and later, you can use the window's rootViewController property to get the current view controller.
[window.rootViewController viewWillAppear];
If you want your application to run on versions prior to iOS 4.0, then you could add an instance variable to the application delegate to remember which view controller called the upload method, having the controller send itself as a parameter.
- (void)upload:(UIViewController *)viewController {
self.uploadingViewController = viewController; // This is the property you add
...
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.uploadingViewController viewWillAppear];
self.uploadingViewController = nil;
}
You should also consider using a different method to reload the buttons, something like reloadButtons, since it is not related to the view appearing in this case. You would then call that method from within viewWillAppear.
Step 1:
In your App Delegate .h file you need to declare a protocol like so:
#protocol AppConnectionDelegate <NSObject>
#required
-(void)connectionFinished:(NSObject*)outObject;
#end
In the same file, add an ivar like so:
id *delegate;
Declare the ivar as a property:
#property (nonatomic, assign) id<AppConnectionDelegate> delegate;
In the App Delegate .m file, synthesize the ivar:
#synthesize delegate;
In the App Delegate .m file, on connectionDidFinishLoading do:
if([self.delegate respondsToSelector:#selector(connectionFinished:)])
{
[self.delegate connectionFinished:objectYouWantToSend];
}
In your viewcontroller's .h file, implement the AppConnectionDelegate by importing a reference to the app delegate file:
#import "AppDelegate_iPhone.h" //if using iPhone
#import "AppDelegate_iPad.h" //if using iPad
In the same file, at the end of the first line of the interface declaration do:
#interface AppDelegate_iPhone : AppDelegate_Shared <AppConnectionDelegate>
Declare ivars accordingly:
AppDelegate_iPhone *appDelegate; //if using iPhone
AppDelegate_iPad *appDelegate; // if using iPad
In your viewcontroller's .m file in the viewDidLoad(), get a reference to your app delegate using:
If iPhone;
appDelegate = (AppDelegate_iPhone*)[[UIApplication sharedApplication] delegate];
If iPad:
appDelegate = (AppDelegate_iPad*)[[UIApplication sharedApplication] delegate];
Then set the viewcontroller to be the delegate in viewDidLoad() by doing:
appDelegate.delegate = self;
Now you need to simply implement the connectionFinished method in the .m file:
- (void)connectionFinished:(NSObject*)incomingObject
{
//Do whatever you want here when the connection is finished. IncomingObject is the object that the app delegate sent.
}
Now whenever your app delegate's connectionDidFinishLoading is called, the view controller will be notified.
[It's a best practice to set appDelegate.delegate = nil if you're done using the connectionFinished callback]
This is tried and tested. If you have questions, leave a comment......
--EDIT--This is a robust alternative to NSNotification. I use both depending on the requirements. The process I use to decide between using NSNotification or a delegate callback using a protocol is simply:
For notifications:
One sender, multiple listeners.
No reference possible between sender and listener.
Complex/multiple objects need not be sent
For delegate callbacks using protocols:
One sender, limited (usually 1) listeners.
A reference between sender and listener is possible.
Complex/multiple objects are to be sent (for example, response objects that need to be sent)
I know sending objects is possible through notifications but I prefer protocols for that.
--EDIT--
Worse comes to worst, you can have both view controllers adhere to a simple one method protocol that will remove that button and refresh the view. Then in your connectionDidFinishLoading method, since you know your view controller must adhere to that protocol, by your design, you simply do something like
ViewController<MyProtocol> curView = (Get the current view controller somehow);
[curview refreshView];

Call rootViewController to switch views within content view (iOS)

I'm working on a pretty simple multiview app for the iOS and I've been following a great tutorial in an Apress book. I've basically got my rootViewController instantiated and displayed with the app delegate, and I've got a number of content viewControllers (6) which I'd like to swap in and out based on user input. However, in the book they perform their switches with a button on a toolbar placed in the rootView using Interface Builder. It fires a method in rootView that loads up the new content ViewController and displays it.
My problem is that I'd like to perform the content view switch (that lies in my rootViewController instance), but I'd like to trigger the switch action with a button that's in my content view (and is therefore unavailable as my File Owner is my contentViewController, whose reference is held inside my rootViewController).
Hopefully I've explained it well enough, please let me know if I should elaborate more. I appreciate any help!
You need to pass down a reference to your root view controller (RootViewController *rootViewController) when you create your content view either in a custom init method or by just assigning it after you created it: self.contentView.rootViewController = self;.
Now inside your content view you can then call the appropriate method in the root view controller to do the switch: [self.rootViewController switchView]. This call then can be triggered inside the method that is called when you press the button (IBAction method).
So this is what you need to do:
1) Create a property inside the your content view controller of type RootViewController
#class RootViewController;
#interface MyContentViewController : NSObject {
#private
RootViewController *rootViewController;
}
#property (retain) RootViewController *rootViewController;
and make sure it retains the reference.
2) Synthesis the property and add the callback to the root view controller that switches the view:
#implementation MyContentViewController
#synthesize rootViewController;
- (IBAction) switchView:(id) sender {
[rootViewController switchToNextView];
}
-(void) dealloc {
[rootViewController release];
[super dealloc];
}
Also release your retain reference at the end.
3) Assign the root view controller to the content view inside your RootViewController:
self.contentViewController = [[[MyContentViewController alloc]
initWithNibName:#"ContentView"
bundle:nil] autorelease];
self.contentViewController.rootViewController = self;
That should be all. I hope that helps you.
Well, you could simply create an IBAction in each of your child controllers that calls:
[self.parentViewController switchToDifferentController:(int) viewNumber]
and then implement the switchToDifferentController method in your root. Other than ignore the compiler warning that parentView might not implement that method, it might work.
However, that is a bit brittle, as you'd have to assume that it was the parent calling you and that nobody will forget to implement that method.
In general, you use the "delegate" concept for a child controller to ask its parent to do something. The general idea is that you declare a group of methods as a "protocol". Think of it as a contract between objects. One object can say "I promise to implement these methods," and another can then choose to send those messages to it. The contract allows the compiler/system to check for conformance. You'll see this in UITableView, where the OS provides a standard table, but it calls back to your code to provide the individual cells as needed.
To implement a protocol, you mustdo the following: (See code segments below
Declares a protocol for the conversation
Specify that the parent will follows that protocol
Create a delegate property in your child
When the parent is about to launch the child, it assigns itself as the delegate for that child.
When the child wants to switch, it calls the parent using that protocol
#protocol myVCDelegate
- (void)switchToDifferentController:(int) viewNumber ;
#end
#interface ParentViewController : UIViewController <VCDelegate>
#property(nonatomic, assign) id <VCDelegate> delegate
childController.delegate = self;
[self.delegate switchToDifferentController:kController5];

MKMapView not calling delegate methods

In a UIViewController I add a MKMapView to the view controlled by the controller.
- (void)viewDidLoad {
[super viewDidLoad];
CGRect rect = CGRectMake(0, 0, 460, 320);
map = [[MKMapView alloc] initWithFrame:rect];
map.delegate = self;
[self.view addSubview:map];
}
Later in the controller I have
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
{
NSLog(#"done.");
}
Done never gets printed. None of the other delegate methods get called either like mapView:viewForAnnotation: I use a MKMapView in an another app, but this seems to happen on any new application I make. Has anyone else seen this behavior?
EDIT:
The problem seems to be when UIViewController is made the delegate of the MKMapView, a direct subclass of NSObject seems to work okay. I can work around like this, still seems very odd since I've done it before.
I had the exact same problem: I had assigned < MKMapViewDeledate> to my controller, and linked the delegate to "File owner" in Interface Builder, but still my implemented delegate methods were not called.
The reason is that since iOS 4, maps are cached, and whenever an app display a cached map then methods such as "mapViewDidFinishLoadingMap" are not called. Resetting "contents and settings" in the simulator clears the cached maps and therefore solves the issue.
A bit old, but since iOS 7 there is a new method that might work. solved my problem
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered
Perhaps you need to go to the IB and control-drag from the MKMapView to the view conroller, then select delegate to make it a delegate???
maybe quite obvious, but just checking:
Have you made sure your viewcontroller declaration is correctly done.
Something like:
#interface YourViewController : UIViewController <MKMapViewDelegate>
I had a similar problem with the methods of MKMapViewDelegate not being called.
My issue was setting the MKCoordinateRegionMakeWithDistance and regionThatFits in the controller's -viewDidLoad() I wanted to only show the region around my house and not start with the world view. So after adding annotations in the controller's viewDidLoad, I started a timer. When it expires in one second, I zoom in on the region I want with the above APIs and the delegate methods fire. It just makes me a little dizzy and inclined to vomit on my iPad.
Now I only have to deal with the darn low memory warnings.
I had a similar issue in XCode 6.1 building for iOS 8.1 where none of the MKMapViewDelegate methods were being called. In a 2nd application the identical code resulted in the MKMapViewDelegate methods being called as expected.
In ViewController.m the mapView delegate was set as follows:
- (void)viewDidLoad {
...
self.mapView.delegate = self;
In ViewController.h:
#interface myMapViewController : UIViewController <MKMapViewDelegate>
#property (strong, nonatomic) IBOutlet MKMapView *mapView;
#end
I had manually added the IBOutlet line above to the header file, but only in the app where the delegate methods were not being received.
The solution was to delete the IBOutlet line I had added manually to the header, and then go to the storyboard and CTRL-drag from the MKMapView to the #interface block in my ViewController.h. The identical IBOutlet was recreated in the header file with the result that all the delegate methods were called correctly.
I just ran into this again on iOS 7 and figured out something that works consistently. The issue we're typically fighting is calling setRegion before the region has context (view rendered). iOS 7 adds new delegate methods representing all tiles being rendered but pre-iOS 7 the cached tiles prevents the delegate method mapViewDidFinishLoadingMap from being called. Instead I use a "pendingRegion" variable to check if I have "queued" a region change for when the map is rendered and then simply use the viewDidAppear method as a signal the map has been rendered to the screen and hence a region will have context.
Add a variable to your header:
MKCoordinateRegion pendingRegion;
Add this method to your code:
-(void)initPendingRegion {
pendingRegion.center = CLLocationCoordinate2DMake(0.0, 0.0);
}
Add this to your viewDidAppear:
if(pendingRegion.center.latitude != 0.0) {
MKCoordinateRegion useRegion = pendingRegion;
[self initPendingRegion];
[self.mapView setRegion:useRegion animated:YES];
}
My problem is that I forget to set location in the simulator. Go to Simulator -> Debug -> Location -> Custom location... and the MKMapViewDelegate methods will start calling
For me, it worked after adding NSLocationWhenInUseUsageDescription string in Info.plist
I also met same issue as yours. I found that: with the iPhone SDK 3.2 - when I create a new UIViewController with an associated xib file (the checkbox option in UIViewController creating dialog), delegate methods of MKMapViewDelegate are never called.
However, when I follow below steps, it runs well.
Create a new UIViewController class (never check the option: create xib file associated with the controller)
Create a new xib file. Add the map using Interface Builder + setting the Owner class with the class on step 1 + setting delegate object of the map point to the owner class.
Implement delegate methods for the UIViewController class in step 1.
When I want to use my ViewController (with the added map), I load it with the nib name as below example:
MapPreviewController* mapPreviewController = [[MapPreviewController alloc]
initWithNibName:#"MapPreviewController" bundle:[NSBundle mainBundle]];
[self.centerPanelView addSubview:mapPreviewController.view];
The code runs ok. I don't know what Apple changes or did with the xib file when I create my UIViewController using the wizard, but it may the root cause of my problem. It takes me 1 day to find this stupid solution.
If you found another one, please share to me.
Thanks

Nib objects (subviews) accessing properties in ViewController

Edited for brevity:
How does a subview access properties in its superview and its superview's view controller? Easy enough to go down the chain. How do we go back up?
Original (verbose) post:
The immediate problem I'm trying to solve is simplifying the "birth" of a somewhat complex view. The large picture has to do with Nibs and how subclasses (of UIView in particular) that have beyond trivial initializers are reconstituted when the view loads.
I have some custom UIViews - subviews of a subview of my viewcontroller's view. When I instantiate these views in particular they need a reference to some properties (NSNumberFormatter, & NSDictionary) of the View Controller.
Currenty I use a method to instantiate them:
- (id)initWithFrame:(CGRect)frame items:(NSDictionary *)dictionary forEditingMode:(EditingMode)mode
I'm experimenting with moving them into a Nib to let them reconstitute themselves and running into basic design issues. One being I think I'm limited to initWithFrame as the default initializer?!? If so how can these objects look into the parent view controller and get a reference to the dictionary and some other properties?
Are there methods I could call within initWithFrame (similar to the ones that retrieve the UIApplication delegate) but would instead allow the child view to send methods to it's parent views and/or controllers?
Send in the MVC police, I'm sure I'm breaking something...
It should work the other way round.
The views only contain controls (text fields, etc.). The data lives in a model, and the view/window controllers mediate, accessing and setting the view controls values, synchronizing with the model.
OK, sometimes you may need to have a dictionary shared between the controller and the view. Then create a dictionary property in the view and set it in the awakeFromNib method of the nib owner.
You can set up outlets that get connected to the view's superview or view controller and pull out the info in awakeFromNib. You need to use awakeFromNib instead of init* because the connections won't be created until then.
FYI, if you instantiate via a Nib, your designated initializer is initWithCoder:, not initWithFrame: (but don't use either for this).
You should probably be doing this through the view controller manually just after the nib is loaded. This means that you will have to set the shared dictionary AFTER the view has been initialized. You might do it like so:
#interface MyViewController : UIViewController {
IBOutlet MyView* view1;
IBOutlet MyView* view2;
IBOutlet MyView* view3;
}
#end
#implementation MyViewController
-(void) viewDidLoad;
{
NSDictionary* sharedDict = //make the shared dictionary here;
view1.sharedDict = sharedDict;
view2.sharedDict = sharedDict;
view3.sharedDict = sharedDict;
}
#end