I have a custom UITableViewCell declared thusly:
#interface ValveCell : UITableViewCell <UINavigationControllerDelegate,UIImagePickerControllerDelegate>
A button which hooks to the following method:
- (void) addPicture {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.delegate = self;
picker.allowsEditing = YES;
// I have tried every possible value for the presentation style
picker.modalPresentationStyle = UIModalPresentationNone;
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
// controller is a #property I added to my custom class
// it is the UIViewController that is the data source/delegate for the table view
[self.controller presentViewController: picker animated: YES completion: nil];
}
And the supporting delegate method:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
[picker dismissViewControllerAnimated:YES completion: nil];
UIImage *chosenImage = info[UIImagePickerControllerEditedImage];
if (chosenImage) {
[self.valve pushPhoto: chosenImage];}
}
There are two issues (at least) I'm having with this code:
Every time I click the button, I get the following warning in my transcript: "Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates." I've tried the various presentation styles. I don't know why this is happening, or what I need to do to make it go away.
It bothers me that I have to hand a controller to the cell (in my tableView:cellForRowAtIndexPath: method). I know walking the superView chain is frowned upon. Is there a no better way to get a controller to open it from? Or an alternate way of opening the picker that doesn't need a controller? Even something like self.tableView.delegate would work if cells but had a back pointer to their tableview.
UPDATE
Answer for #1 goes to #Jiten Parmar below. As for #2, I found the PPTopMostController pod which allowed me to use the code:
[UIViewController topMostController]
which worked like a charm.
"Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates."
The answer is that when your app is in the portrait mode and you open the image picker with landscape it will show this warning in the console, but no need to worry that, it is not critical for your app and no crash issue will be there.
Related
I have a question regarding view hierarchy:
I currently have a HomeViewController that is loaded into view after a login screen. The HomeViewController has a ContainerView, which is embedded with a SubViewController. This container is initially hidden when the home view is loaded. When I trigger an action (via a UIButton) to "unhide" the container, I am trying to have an image load in that SubViewController that was rendered in the HomeViewController during viewDidLoad.
The problem I am facing is that for whatever reason, I can not seem to have the image display in the SubViewController.
FYI: The way I am rendering this image is such:
Upon the HomeViewController's viewDidLoad, I am taking a screen shot programmatically of a UIView that is generated from saved user data. This UIView is displayed in the view of the HomeViewController.
When the user clicks a button, I am unhiding and now showing the SubViewController, the view embedded in the ContainerView.
I can not seem to get the screen shot image (saved as imageSS) to appear in the SubViewController. I have tried to suppress the viewDidLoad method for the SubViewController when the HomeViewController is loaded, then call the SubViewController viewDidLoad when the button is pressed to show it, but no luck there either.
This might be confusing but it should be easy - I am at a loss why I can't get this to work. If anyone can help me out I would appreciate it. Thanks!
Here is the code i am currently trying: this method is in the HomeViewController.m
- (void)takeScreenshot:(id)sender {
UIGraphicsBeginImageContextWithOptions(_homeRenderImage.bounds.size, NO, 0.0);
[_homeRenderImage drawViewHierarchyInRect:_homeRenderImage.bounds afterScreenUpdates:YES];
UIImage *imageSS = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
SubViewController *pvc = [[SubViewController alloc] init];
userImage = imageSS;
[pvc loadImage];
}
Here is the method I am using in the SubViewController.m:
NOTE: userImage is a shared property between these controllers. I have tested that this sharing works
- (void)loadImage {
_subImage.image = userImage;
}
I feel dumb - all I needed to do was to call the loadImage method in the viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear: animated];
[self loadImage];
}
Using the following code:
- (void) viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (!self.alreadyAppeared) {
[self performSelector:#selector(showCamera) withObject:nil afterDelay:0.3];
}
}
-(void)showCamera
{
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
picker.showsCameraControls = YES;
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
picker.delegate = self;
[self presentViewController: picker animated:YES completion:NULL];
}
my app works perfectly. However if I change the
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;
to
picker.cameraDevice = UIImagePickerControllerCameraDeviceRear;
I lose the image. The following code is how I capture the image and set an imageview within my app. I use a Nav controller to return to the app:
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
self.cf.imageView.image = chosenImage;
NSLog(#"%#",chosenImage);
[picker dismissViewControllerAnimated:YES completion:NULL];
// pop back to previous controller
NSArray *myControllers = self.nc.viewControllers;
int previous = (int)myControllers.count - 2;
UIViewController *previousController = [myControllers objectAtIndex:previous];
[self.nc popToViewController:previousController animated:YES];
}
Again, this works just fine on my iPad and it works just fine using the front camera. Even when using the rear camera the chosen image (that I log) appears correct. Does anybody have any idea why the rear camera setting would have such an effect?
One other item: I get this very irritating message that has about 1,000 different explanations none of which get rid of the message:
Snapshotting a view that has not been rendered results in an empty snapshot. Ensure your view has been rendered at least once before snapshotting or snapshot after screen updates.
When using the iPhone 6 and 6 plus, you must prevent your views from reloading each time you navigate away from the camera control (picker). My app uses a View Controller that creates a dynamic view which is a form. Each field on that form is pulled from a web service and becomes a subview of the form. That includes the camera field, which I use a viewcontroller and xib to build the field. The form view builds the entire form through a drawRect method. The camera field attempts to call that method each time it navigates away. I prevent it from reloading and that appears to have solved the issue.
A more standard app may create the camera field dynamically in viewDidAppear method of the main view controller. If you have the main view controller set as the delegate for the camera field, it will still call the viewDidAppear when it navigates away from the picker. That must be prevented or you will lose the image.
The really funny thing here is that this only happens on iPhone 6 and 6 plus. All other devices do not attempt to reload the views ... go figure.
My app allows users to import video and perform a few other actions like send an email. When I call the standard code to present these view controllers (example below), the result is that scrollable elements appear underneath the status bar, which is hideous. My own View Controllers do not suffer from this glitch. The problem seems to occur in iOS 7 and 8.
I see many related questions, but most of them address dealing with the status bar in your own view controllers, and I've already done that. My question is about dealing with view controllers I get from the OS.
Any idea what we might be doing (e.g. in storyboard) to cause this? Did I forget or miss an obvious step? It's very ugly and distracting and a fix would be fantastic. I don't see this in any other app.
UIImagePickerController *mediaUI = [[UIImagePickerController alloc] init];
mediaUI.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
// Displays saved movies from the Camera Roll album.
mediaUI.mediaTypes = #[(__bridge NSString *)kUTTypeMovie];
mediaUI.allowsEditing = YES;
mediaUI.delegate = self; //with or without this, the glitch appears
[self presentViewController:mediaUI animated:YES completion:nil];
Here's what it looks like:
The only solution I could find was to remove the status bar. This might be possible via davidisdk's answer to this question, but I did it by subclassing UIImagePicker (and a few other classes) like this:
#interface NoStatusBarImagePickerController : UIImagePickerController
#end
#implementation NoStatusBarImagePickerController
- (BOOL)prefersStatusBarHidden {
return YES;
}
- (UIViewController *)childViewControllerForStatusBarHidden {
return nil;
}
#end
Problem
It looks like either the UITableView insets/frame is wrong.
The first cell is partially hidden by the navigation controller.
Notice that I would like to avoid subclassing UIImagePickerController as Apple documentation states that:
This class is intended to be used as-is and does not support subclassing
Screenshot
Configuration
OS: iOS 7.1
Device: iPhone 4s (not simulator)
Code
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imagePicker.mediaTypes = [[NSArray alloc] initWithObjects:(__bridge NSString *)kUTTypeMovie, nil];
imagePicker.delegate = self;
[imagePicker setVideoQuality:UIImagePickerControllerQualityType640x480];
[self presentViewController:imagePicker animated:true completion:nil];
Fix Attempts That Failed
I've tried to make sure that insets are automatically managed:
imagePicker.automaticallyAdjustsScrollViewInsets = YES;
I've tried to verify that the VC is aware of the navigation bar:
imagePicker.navigationBarHidden = NO;
The following fixes the issue but I'd like the navigation bar to remain translucent:
[imagePicker.navigationBar setTranslucent:NO];
This will happen with tableview, when you are scrolling tableview this is a normal behaviour. If you want it to properly align you can use the tableview method to do that
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:topRowValue inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
You will need to have the delegate of the tableview set to self and when the tableview has done scrolling, check which cell is on top and align it to the top using the above method. However, this will not guarantee the results as you desire. The reason for that is, you have different screen sizes for iphone4 and iphone5, say you have 5 cells that are visible in iphone5, in iphon4 it can happen that you see 4 cells completely and a part of the 5th cell in, as you are seeing now. So, if it isn't absolutely necessary, you can leave it as is.
self.tableview.automaticallyAdjustsScrollViewInsets = YES; try this.
You can get access to this table view by conforming your class to protocol UINavigationControllerDelegate
extension YourController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
guard let photosTableView = viewController.view.subviews[0] as? UITableView else { return }
}
}
You can do it using Storyboard as Follows...
1- Select your controller on storyboard
2- go to attribute inspector.
3- Uncheck "Adjust Scroll View view insets".
I have the following piece of code to add a QLPreviewController subview
{
QLPreviewController *preview = [[QLPreviewController alloc] init];
preview.delegate = self;
preview.dataSource = self;
[self addChildViewController:preview];
[self.view addSubview:preview.view];
[preview didMoveToParentViewController:self];
self.previewController = preview;
}
-(NSInteger) numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller
{
return 1;
}
-(id) previewController:(QLPreviewController *)previewController previewItemAtIndex:(NSInteger)index
{
return self.url;
}
self.url is an NSURL that is located in NSTemporaryDirectory - file://localhost//.../blah.pdf
My issue is that when my laptop is connected to the internet, the document shows up as a subview, but when my laptop is not connected, numberOfPreviewItemsInPreviewController & previewItemAtIndex do not get called.
I've tried a vanilla program with a simple view controller, and it seemed to work fine. (My app is more complex than that).
When I try presenting the document as a modal view, it seems to work irrespective of whether or not the simulator is connected to the internet.
[self presentViewController:preview animated:NO completion:nil]; --> works consistently.
I need to get the subview working for online & offline modes, it would be great if someone could help!
You might be encountering strange behaviour because the QLPreviewController's view is not designed to be embedded in another view. From the QLPreviewController class reference overview:
To display a Quick Look preview controller you have two options: You can push it into view using a UINavigationController object, or can present it modally, full screen, using the presentModalViewController:animated: method of its parent class, UIViewController.
Having said that, you could try:
Forcing the QLPreviewController to (re)display its contents. Try adding [self.previewController reloadData]; to the end of your first method. This should force the data source method(s) to fire.
Forcing the view to "refresh" it's subviews: [self.view setNeedsLayout] (which may in fact force a reloadData like the first option).
Good luck!