Background:
I've got a free app that is ad-supported. The basic idea is that when the app is launched, ad content (HTML) is downloaded from the server in the background, and only if the download was successful, then the content is being shown.
Previous solution:
I have implemented this successfully in a universal app. Originally, I loaded a NSData variable with an URL, and if it was successful, then I presented a modal view which contained an UIWebView (relevant code is in another question). The problem is that the external assets (images) don't get loaded in the original NSData request, so in a stroke of genius I thought of another way:
New solution:
The new way, and this is what I want help with, is that I'm instantiating the modal view controller (without displaying it yet), and having the web view begin loading the URL directly. When the webViewDidFinishLoad method is called, I fire a notification back to the parent which then performs the presentModalViewController.
Here are the methods which instantiate the view controller and present it modally:
- (void)launchAd
{
if ([ServerCheck serverReachable:#"openx.freewave-wifi.com" hideAlert:YES])
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(presentAdModal) name:#"AdLoaded" object:nil];
AdViewController *adView = [[AdViewController alloc] initWithNibName:nil bundle:nil];
[[adView view] awakeFromNib]; //THIS LINE IS WHAT THIS QUESTION IS ABOUT
navController = [[UINavigationController alloc] initWithRootViewController:adView];
[adView release], adView = nil;
[navController setModalPresentationStyle:UIModalPresentationFormSheet];
[navController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[[navController navigationBar] setTintColor:[UIColor colorWithRed:0.94 green:0.00 blue:0.32 alpha:1.00]];
}
else
LogError(#"Not presenting ad.");
}
- (void)presentAdModal
{
LogInfo(#"Presenting advertisement modal.");
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"AdLoaded" object:nil];
[tabBarController presentModalViewController:navController animated:YES];
[navController release];
}
And in the AdView controller:
- (void)webViewDidFinishLoad:(UIWebView *)theWebView
{
LogInfo(#"Ad content did load; we can show it.");
[timeout invalidate], timeout = nil;
[self setTitle:#"From Our Sponsor"];
[[NSNotificationCenter defaultCenter] postNotificationName:#"AdLoaded" object:nil];
}
Now what's above does work. However, it took a long time for me to figure out how to cause the view controller delegate methods in the AdView controller to fire before calling presentModalViewController. I did this by adding [[adView view] awakeFromNib];, but I know this isn't the right way to make this happen.
So, everything above does work beautifully, effectively having the view controller and its UIWebView "preload" before displaying it. I just want to know what I should be doing instead of [[adView view] awakeFromNib]; -- I want to do it the right way.
Or is this right? I guess some folks would say "If it works, then it's right", but I know this isn't true.
Related
So I have a Universal link that leads to a view controller in my app. On that particular view controller I display a couple of images as well as a web view. The webView displays a url chosen by the user. How do I save this custom url so that it is displayed every time someone clicks the link? I think the code to this is under:
#synthesize deepLinkingCompletionDelegate;
-(void)configureControlWithData:(NSDictionary *)data {
NSString *string = data[#"favoriteArticle"];
Alex from Branch.io here:
To accomplish this, you need to do two things.
Step 1
Store the URL of the article you want to load as one of the Branch link custom parameters. Full instructions on how to do that here, but essentially:
BranchUniversalObject *branchUniversalObject = [[BranchUniversalObject alloc] initWithCanonicalIdentifier:#"item/12345"];
branchUniversalObject.title = #"My Content Title";
branchUniversalObject.contentDescription = #"My Content Description";
branchUniversalObject.imageUrl = #"https://example.com/mycontent-12345.png";
[branchUniversalObject addMetadataKey:#"favorite_article" value:#"https://example.com/path/to/article"]; // this is used to route inside the app
[branchUniversalObject addMetadataKey:#"property2" value:#"red"];
BranchLinkProperties *linkProperties = [[BranchLinkProperties alloc] init];
linkProperties.feature = #"sharing";
linkProperties.channel = #"facebook";
[linkProperties addControlParam:#"$desktop_url" withValue:#"https://example.com/path/to/article"]; // this is used for desktop visitors
[linkProperties addControlParam:#"$ios_url" withValue:#"https://example.com/path/to/article"]; // this is used for iOS mobile visitors without the app installed
Step 2
Then when the app opens after a link click, watch for that data key. Again, full instructions, but basically:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// initialize the session, setup a deep link handler
[[Branch getInstance] initSessionWithLaunchOptions:launchOptions
andRegisterDeepLinkHandler:^(NSDictionary *params, NSError *error) {
// start setting up the view controller hierarchy
UINavigationController *navC = (UINavigationController *)self.window.rootViewController;
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
UIViewController *nextVC;
// If the key 'favoriteArticle' is present in the deep link dictionary
// then load the picture screen with the appropriate picture
NSString * favoriteArticle = [params objectForKey:#"favorite_article"];
if (favoriteArticle) {
nextVC = [storyboard instantiateViewControllerWithIdentifier:#"ArticleVC"];
[nextVC setArticleUrl: favoriteArticle];
} else {
nextVC = [storyboard instantiateViewControllerWithIdentifier:#"MainVC"];
}
// navigate!
[navC setViewControllers:#[nextVC] animated:YES];
}];
return YES;
}
After this, in your ArticleVC, retrieve the favoriteArticle value and use it for your webview.
Step 2 (Alternate)
The configureControlWithData method you mentioned is used in the automatic deep link routing implementation. You may be able to adapt this to work with a webview, but I haven't personally tried that. It would look something like this:
#synthesize deepLinkingCompletionDelegate;
- (void)configureControlWithData:(NSDictionary *)data {
NSString *favoriteArticle = data[#"favorite_article"];
// load the webview with the URL stored inside favoriteArticle
}
I currently have a map displaying 10 or so co ordinates.The map gets the users location and centers on it as soon as it is opened. When panning the page or zooming different levels it eventually resets and centers in on the first position of the user.I have tried "stopupdating location" and Animated as "NO".I can not get it to stay in positon when the user scrolls the map.
- (void)viewDidLoad {
[super viewDidLoad];
self.petrolMap.delegate = self;
self.location = [[CLLocationManager alloc] init];
[location setDelegate:self];
[location setDistanceFilter:0]; // Do not apply a distance filter to the map
[location setDesiredAccuracy:kCLLocationAccuracyBest]; // Use the best accuracy possible when displaying the map
petrolMap.delegate=self; // Display on "Petrol Map" , the mapview for the application}
-(void) mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{MKCoordinateRegion mapRegion;
mapRegion.center = petrolMap.userLocation.coordinate;
mapRegion.span.latitudeDelta=0.02;
mapRegion.span.longitudeDelta=0.02;
[petrolMap setRegion:mapRegion animated:NO];}
Your 'location' is a location manager, when it works out where you are it'll send its delegate
locationManager:didUpdateToLocation:fromLocation:
which you don't seem to have, so all those settings you're doing to 'location' are wasted (as far as the code you've given us, it may be useful elsewhere) and telling it to stop tracking the user is of no use.
"(void) mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{MKCoordinateRegion mapRegion;" is what petrolMap is sending to its delegate. Somewhere you must have set petrolMap to track the user, it can be done in the .xib.
Anyway, to stop petrolMap sending messages make sure you run
[petrolMap setUserTrackingMode:MKUserTrackingModeNone animated:NO];
Some extra notes:
Within didUpdateUserLocation you don't need to refer to petrolMap directly because the mapView parameter is set to which ever MKMapView sent the message.
Also within didUpdateUserLocation you are using petrolMap's userLocation instead of the parameter userLocation, and even building your region. The entire code for that function could be one line
[mapView setRegion:mapRegion animated:NO];
'Animated' controls how the change in region is done. Yes means it will slide between locations, No means it will snap from one to the other instantly, either way the map will move to the new region.
Your viewDidLoad method could be cut to two lines like follows
[super viewDidLoad];
self.petrolMap.delegate = self;
Addendum:
locationManager:didUpdateToLocation:fromLocation
is deprecated in iOS6.
Unfortunately this is a few years to late for you James - but hopefully it'll help others who are stuck in this situation (like myself).
I ended up adding...
[self.mapView setUserTrackingMode:MKUserTrackingModeFollow];
Into my -(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
You should normally add "animated:YES" at the end, but this again would ping it back to my current location, even if I changed the commander to "NO" - so tried deleting it and it worked!!!
Just for reference my whole code became:
-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
if(userLocationShown) return;
MKCoordinateRegion region;
MapView.showsUserLocation = YES;
region.center.latitude = MapView.userLocation.coordinate.latitude;
region.center.longitude = MapView.userLocation.coordinate.longitude;
region.span = MKCoordinateSpanMake(0.02,0.02);
[MapView setRegion:region animated:YES];
[self.mapView setUserTrackingMode:MKUserTrackingModeFollow];
[locationManager startUpdatingLocation];
[locationManager stopUpdatingLocation];
userLocationShown = YES;
and I added...
- (void)viewDidLoad {
[super viewDidLoad];
[locationManager setDistanceFilter:kCLDistanceFilterNone];
[locationManager setDesiredAccuracy:kCLLocationAccuracyBest];
MapView.delegate = self;
I have a page that checks if there is a list of employees on the bundle, and if there is it displays them in a table view. But if there is no list on the bundle it throws up a modal view controller. That then requires someone to login, the login is authenticated and then data is downloaded.
The ModalView is setup to be a delegate of the first page. I can call the delegate method just fine and pass the list, but when the ModalView is dismissed the table does not reload the tableview with the data. If i run the project again it loads the list up in the table view instantly.
Here is the method on the ViewDidLoad of the first page
[self checkLastLoginDate];
[self loadDataIntoArray];
if (currentData && dataLoadedIntoArray) {
NSLog(#"sweet the data is current");
[self createIndexedArray];
}
else{
NSLog(#"Data not loaded and not current");
//push login view
///if ipad do this
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
//the above code was working fine. I am just testing pushing a dynamic xib
LoginViewControlleriPad *loginControlleriPad = [[LoginViewControlleriPad alloc] initWithNibName:#"LoginViewControlleripad" bundle:nil];
loginControlleriPad.modalPresentationStyle = UIModalPresentationFormSheet;
loginControlleriPad.delegate = self;
[self presentModalViewController:loginControlleriPad animated:YES];
[self createIndexedArray];
[loginControlleriPad dismissModalViewControllerAnimated:NO];
[self.tableView reloadData];
}
The first two methods check to make sure the data exists on the bundle and that the last login date is within 15 days.
if it is then I create an IndexedArray and it displays nicely.
If the data is not on the bundle or the date is to old I check the device and use a modal view. The delegates are set and the ModalView Appears.
In the ModalView I use a syncronus and asyncronus request to hit a server for the required information. Then I create the list and pass it back to the first page.
Once the connection is made and we do a little work on the list we save it to the bundle.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:kEMPLOYEE_PLIST];
/////write to file/////
[cleanResults writeToFile:path atomically:YES];
employeeList = [[NSMutableArray alloc ]initWithContentsOfFile:path];
if (employeeList != nil)
{
[delegate passUserInfo:employeeList];
//cant get login view to dismiss
//[self dismissModalViewControllerAnimated:YES];
}
Once I get the list I right it to file and then read it back in. If there is info in the list I call the delegate method.
UPDATE
Here is the delgate method
- (void)passUserInfo: (NSMutableArray *)employeeDataArray
{
employeeData = [[NSMutableArray alloc] initWithArray:employeeDataArray];
[self.tableView reloadData];
/////FIX////////
[createIndexedArray];
}
Once that is called nothing happens. I tried dismissing the modal view, which works, but then the first page does not refresh and my app gets lost in the oblivion. I am just confused at were it is going and how i can refresh the tableview on the first page.
Is there a step I are missing? I have tried tracking it and lose it after this step.
I actually figured out that my code was setup right, i just needed to call a method that sorted the array into an alphabetic array. Once i called that method from the delegate method it worked like a snake charm.
UPDATE
I had a method that created an indexed array. When i called that inside the delegate method it reloaded the page and inserted the new into the table view. I am pretty sure it was reloading the whole time, i was just not calling the method that was populating the array that was being being displayed in the tableview.
I make a program where I sometimes moves some anchor to another
When I move those anchors I would recompute distance of bizs nearby the 2 anchors (before and after anchors). The computation is done in background
I used this standard code to update stuff
+(void)commit {
// get the moc for this thread
[Tools breakIfLock];
NSManagedObjectContext *moc = [self managedObjectContext];
NSThread *thread = [NSThread currentThread];
DLog(#"threadKey commit%#" , [[self class]threadKey]);
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidSave:) name:NSManagedObjectContextDidSaveNotification object:moc];
}
NSError *error;
if (![moc save:&error]) {
CLog(#"Error in Saving %#", error);
DLog(#"What the hell error is it");
}
else{
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSManagedObjectContextDidSaveNotification object:moc];
}
//[GrabClass StopNetworkActivityIndicatorVisible];
}
+(void)contextDidSave:(NSNotification*)saveNotification {
dispatch_async(dispatch_get_main_queue(), ^{
BadgerNewAppDelegate *delegate = [BNUtilitiesQuick appDelegate];
DLog (#"currentThreadinContextDidSave: %#",[self threadKey]);
NSManagedObjectContext *moc = delegate.managedObjectContext; //delegate for main object
CLog(#"saveNotification : %#",saveNotification);
[moc mergeChangesFromContextDidSaveNotification:saveNotification];
});
//[moc performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:) withObject:saveNotification waitUntilDone:YES];
}
I break point and see that distances did get updated. Everything is fine
However the NSFetchedResultsController fetchedObjects doesn't seem to get updated and still use the old value.
How can that be?
Also the
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
DLog(#"controllerWillChangeContent: %#", controller);
[self.tableViewA beginUpdates];
}
is never called even though the NSManagedObjectContext has changes.
Well actually I wasn't sure if the managedObjectContext has changed or not. How do I know? I mean will change in managedObjectContext ensure changes in fetchController.fetchedObjects.
There is no caching as far as I know. How can I be sure of that too?
The NSFetchedResultsController documentation for fetchedObjects property states:
The results array only includes instances of the entity specified by
the fetch request (fetchRequest) and that match its predicate. (If the
fetch request has no predicate, then the results array includes all
instances of the entity specified by the fetch request.)
The results array reflects the in-memory state of managed objects in
the controller’s managed object context, not their state in the
persistent store. The returned array does not, however, update as
managed objects are inserted, modified, or deleted.
Availability Available in iOS 3.0 and later.
I can't say what the appropriate workaround is. My first thought is to call performFetch: in controllerDidChangeContent: in the delegate implementation.
The fetchedObjects array appears to update simply by overriding controllerDidChangeContent: with an empty implementation. This is the case using both the iPad and the iPad simulator for iOS 5.1.
There's clearly some discrepancy between the documentation and what I have observed. I have no explanation. Sorry. I can only suggest that you perform the fetch in controllerDidChangeContent: just to be safe.
I'm attempting to present a UITabBarController modally using the following code:
// Declare all view controllers.
TabOne *tabOne = [[TabOne alloc] initWithNibName:#"TabOne" bundle:nil];
TabTwo *tabTwo = [[TabTwo alloc] init];
TabThree *tabThree = [[TabThree alloc] init];
// Set each view controller's delegate to self.
tabOne.delegate = self;
tabTwo.delegate = self;
tabThree.delegate = self;
// Set a title for each view controller.
tabOne.title = #"One";
tabTwo.title = #"Two";
tabThree.title = #"Three";
// Create a tab bar controller.
UITabBarController *tabBarController = [[UITabBarController alloc] init];
[tabBarController setViewControllers:[NSArray arrayWithObjects:tabOne,tabTwo,tabThree, nil]];
// Present the tab bar controller modally.
[self presentModalViewController:tabBarController animated:NO];
// Memory management.
[tabOne release];
[tabTwo release];
[tabThree release];
This all works as expected except that I get the following warnings in the console:
Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate.
I've done some research on this and have checked that shouldAutorotateToInterfaceOrientation is implemented as follows:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
As far as I can tell, the problem is that the tab bar controller is not the root view controller, but I'm presenting this modal view some way into a deep view hierarchy. It's called from another modal view, which itself is called from a tab bar set in the application delegate.
I know this is a bit of an old chestnut, but it's got me stumped. Any thoughts?
Thanks in advance.
I've had a similar problem.
UITabBarController has some odd behavior with its orientation handling. When setting its orientation, it calls recursively into self.selectedViewController to decide whether to use one-stage or two-stage animation. That seems sensible, but the trouble is self.selectedViewController is initially nil (in particular, if you're displaying the UITabBarController modally for the first time), and that can confuse the controller. Depending on the iOS version, a nil selectedViewController will lead the UITabBarController to believe that one-stage animation is unsupported.
Try this: when you first load / initialize your UITabBarController, add the line
tabBarController.selectedIndex = 0;
I was getting this warning (including serious visual problems: the view controller was switching orientation but the status bar was not), and setting the index this way fixed the problem. The UITabBarController successfully called into its selected view controller, and detected the one-stage animation.