I have this simple code for camera View controller:
UIImagePickerController picker = new UIImagePickerController();
picker.PrefersStatusBarHidden ();
picker.SourceType = UIImagePickerControllerSourceType.Camera;
UIImagePickerControllerCameraDevice dev = picker.CameraDevice;
PresentViewController (picker, false, null);
picker.FinishedPickingMedia += (object sender, UIImagePickerMediaPickedEventArgs e) => BeginInvokeOnMainThread (delegate {DismissViewController (false, null);});
When app starts, I can capture photo normally, but when i present picker again, camera View appears but frame(image) from previous shot is shown and frozen. If i move my device around image doesn't change. In other words, I can use camera once but I can not use it twice. What I am doing wrong? On iOS6 devices it works perfectly.
Making a pickerDelegate class did the trick for me. You just have to pass the current VC in the constructor so you can handle the image in your VC.
PickerDelegate
private class pickerDelegate : UIImagePickerControllerDelegate
{
private yourVC _vc;
public pickerDelegate (yourVC controller) : base ()
{
_vc = controller;
}
public override void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo)
{
//Do something whit the image
_vc.someButton.SetBackgroundImage (image, UIControlState.Normal);
//Dismiss the pickerVC
picker.DismissViewController (true, null);
}
}
ViewDidLoad
imagePicker = new UIImagePickerController ();
//Set the Delegate and pass the current VC
imagePicker.Delegate = new pickerDelegate (this);
Related
I am trying to attach a UITapGestureRecognizer to the View of a UIAlertController but the recognize event is never firing.
I am coding this in Xamarin, but I feel like the issue applies to native code as well.
InvokeOnMainThread(() =>
var alert = new UIAlertController();
alert.Title = "My Title";
alert.Message = "My Message";
UITapGestureRecognizer tapGestureRecognizer = new
UITapGestureRecognizer((gesture) =>
{
//I never get here
});
alert.View.AddGestureRecognizer(tapGestureRecognizer);
alert.View.UserInteractionEnabled = true;
this.PresentViewController(alert, true, null);
});
Ideally, I would like to dismiss the alert when a user touches the popup, but I can't seem to detect the gesture.
I've tried adding the recognizer both, before, and after the alert is presented.
Solution:
To dismiss the UIAlertController by clicking the background View, you can add the tapGestureRecognizer to the last view in the screen when UIAlertController is popping up, check the code below:
public partial class ViewController : UIViewController
{
public ViewController (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad ()
{
base.ViewDidLoad ();
// Perform any additional setup after loading the view, typically from a nib.
var alert = new UIAlertController();
alert.Title = "My Title";
alert.Message = "My Message";
UIAlertAction ac1 = UIAlertAction.Create("123",UIAlertActionStyle.Cancel,null);
alert.AddAction(ac1);
this.PresentViewController(alert, true, addGesOnBackGround);
}
public void addGesOnBackGround() {
UIView backView = new UIView();
Array arrayViews = UIApplication.SharedApplication.KeyWindow.Subviews;
if (arrayViews.Length >0)
{
backView = arrayViews.GetValue(arrayViews.Length-1) as UIView;
}
UITapGestureRecognizer tapGestureRecognizer = new
UITapGestureRecognizer((gesture) =>
{
//I never get here
this.DismissViewControllerAsync(true);
});
backView.AddGestureRecognizer(tapGestureRecognizer);
backView.UserInteractionEnabled = true;
}
}
I'm trying to make a AVPlayerViewController go to full screen mode programmatically, coming from "embedded" mode, however this does not appear to be possible with the published API.
Is there a workaround that I'm missing? I'm interested in obtaining the same animation to the one that you get when the user presses the full screen button on the bottom right of the controls.
Using MPMoviePlayerController is not a viable alternative since I might have more than one video playing at a time.
Thanks.
AVPlayerViewController is a subclass of UIViewController, so it is presentable like any other view controller subclass. Are you able to use presentViewController:animated:completion?
self.avPlayerController.modalPresentationStyle = UIModalPresentationOverFullScreen;
[self presentViewController:self.avPlayerController animated:YES completion:nil];
This then shows the "Done" button in the top left-hand corner.
Updated for iOS 11
There is no supported way to programmatically go fullscreen with AVPlayerViewController (a bit of an oversight in my opinion).
However, AVPlayerViewController does contain a private method that does exactly that. You'll have to decide for yourself whether you'd want to use it or not given you're not supposed to call private methods.
AVPlayerViewController+Fullscreen.h
#import <AVKit/AVKit.h>
#interface AVPlayerViewController (Fullscreen)
-(void)goFullscreen;
#end
AVPlayerViewController+Fullscreen.m
#import "AVPlayerViewController+Fullscreen.h"
#implementation AVPlayerViewController (Fullscreen)
-(void)goFullscreen {
NSString *selectorForFullscreen = #"transitionToFullScreenViewControllerAnimated:completionHandler:";
if (#available(iOS 11.3, *)) {
selectorForFullscreen = #"transitionToFullScreenAnimated:interactive:completionHandler:";
} else if (#available(iOS 11.0, *)) {
selectorForFullscreen = #"transitionToFullScreenAnimated:completionHandler:";
}
SEL fsSelector = NSSelectorFromString([#"_" stringByAppendingString:selectorForFullscreen]);
if ([self respondsToSelector:fsSelector]) {
NSInvocation *inv = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:fsSelector]];
[inv setSelector:fsSelector];
[inv setTarget:self];
NSInteger index = 2; //arguments 0 and 1 are self and _cmd respectively, automatically set
BOOL animated = YES;
[inv setArgument:&(animated) atIndex:index];
index++;
if (#available(iOS 11.3, *)) {
BOOL interactive = YES;
[inv setArgument:&(interactive) atIndex:index]; //arguments 0 and 1 are self and _cmd respectively, automatically set by NSInvocation
index++;
}
id completionBlock = nil;
[inv setArgument:&(completionBlock) atIndex:index];
[inv invoke];
}
}
#end
UPDATE: Swift 4 version of ToddH's answer:
private func enterFullscreen(playerViewController: AVPlayerViewController) {
let selectorName: String = {
if #available(iOS 11.3, *) {
return "_transitionToFullScreenAnimated:interactive:completionHandler:"
} else if #available(iOS 11, *) {
return "_transitionToFullScreenAnimated:completionHandler:"
} else {
return "_transitionToFullScreenViewControllerAnimated:completionHandler:"
}
}()
let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)
if playerViewController.responds(to: selectorToForceFullScreenMode) {
playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
}
}
In iOS11 there are 2 new properties for AVPlayerViewController: entersFullScreenWhenPlaybackBegins and exitsFullScreenWhenPlaybackEnds. You can enable full screen mode right after playback begins and disable it when playback ends with these properties. If you need to enable fullscreen mode after some delay you can use private API methods as ToddH mentioned in his answer. However in iOS11 _transitionToFullScreenViewControllerAnimated:completionHandler: method is not available anymore, there is the same method called _transitionToFullScreenAnimated:completionHandler:. The second method accepts the same arguments as the first one.
I can show an example how to use it. First of all you need to create AVPlayerViewController instance in your UIViewController:
private let playerController : AVPlayerViewController = {
if let urlForPlayer = URL(string: "your_video_url") {
$0.player = AVPlayer(url: urlForPlayer)
}
return $0
} (AVPlayerViewController())
Then you need to setup view for AVPlayerViewController and add it to your current controller view. Function setupAVplayerController can do it for you:
private func setupAVplayerController() {
self.addChildViewController(self.playerController)
self.playerController.view.frame = CGRect(x: 0.0, y: 0.0, width: 200.0, height: 200.0)
self.view.addSubview(self.playerController.view)
self.playerController.didMove(toParentViewController: self)
}
Function enterFullscreen forces full screen mode for AVPlayerViewController:
private func enterFullscreen(playerViewController:AVPlayerViewController) {
let selectorName : String = {
if #available(iOS 11, *) {
return "_transitionToFullScreenAnimated:completionHandler:"
} else {
return "_transitionToFullScreenViewControllerAnimated:completionHandler:"
}
}()
let selectorToForceFullScreenMode = NSSelectorFromString(selectorName)
if playerViewController.responds(to: selectorToForceFullScreenMode) {
playerViewController.perform(selectorToForceFullScreenMode, with: true, with: nil)
}
}
And now you need to call all these functions where you need it, for example in viewDidAppear:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//Your code
self.setupAVplayerController()
self.playerController.player?.play()
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
self.enterFullscreen(playerViewController:self.playerController)
}
}
Don't forget that this solution based on private API calls that is not recommended to use.
As a little iOS 14 update to ToddH's answer: the private API to call is enterFullScreenAnimated:completionHandler:. So here's an extension on AVPlayerViewController to enter full screen.
extension AVPlayerViewController {
func enterFullScreen(animated: Bool) {
perform(NSSelectorFromString("enterFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
func exitFullScreen(animated: Bool) {
perform(NSSelectorFromString("exitFullScreenAnimated:completionHandler:"), with: animated, with: nil)
}
}
This implementation doesn't offer a completion callback. If you pass a Swift closure to the completionHandler parameter, it crashes in the underlying Obj-C API. I haven't investigated how to pass the closure to make it work.
Swift 3 version for the answer of ToddH:
extension AVPlayerViewController {
func goFullScreen() {
let selector = NSSelectorFromString("_transitionToFullScreenViewControllerAnimated:completionHandler:")
if self.responds(to: selector) {
// first argument is animated (true for me), second is completion handler (nil in my case)
self.perform(selector, with: true, with: nil)
}
}
}
You can just set the videoGravity property of AVPlayerViewController.
if(fullscreen)
{
[self.avPlayerController
setVideoGravity:AVLayerVideoGravityResizeAspectFill];
}
else
{
[self.avPlayerController
setVideoGravity:AVLayerVideoGravityResizeAspect];
}
For an 'embedded' AVPlayerViewController instance, it is quite easy to programmatically have it start playback in full screen mode, and without hacking anything (calling private methods). You just need to set its entersFullScreenWhenPlaybackBegins property to true.
You need to add the controller as a child VC to the main VC, and that's basically it. In viewDidAppear(_:) you need to call play() method on the controller's player property - playback will be automatically started in fullscreen.
It's often best to check Apple sample code for these kind of tricky APIs; I think this one might be useful for a lot of AVPlayer use cases: Using AVKit in iOS.
I did not have the need to use any restricted code.
For this, I am assuming that you have added the AVPlayerViewController as a child view controller.
Then for that you will first have to remove the child view controller and then present it again as a fullscreen controller as well attach the AVPlayer view properly to it's parent view.
Here is how I did it. Please note that I am using a library called Easy Peasy for restoring the playerVC.view constraints - one can do that with proper constraints as well.
#objc func fullscreenButtonClicked() {
playerVC.willMove(toParentViewController: nil)
playerVC.view.removeFromSuperview()
playerVC.removeFromParentViewController()
self.present(self.playerVC, animated: false, completion: {
self.playerVC.view.easy.layout(Top(), Right(), Left(), Bottom())
})
}
Its pretty simple, just set
playerViewController.videoGravity = .resizeAspectFill
and it goes full screen:)
I have a very standard implementation of UIActivityViewController. When I use Twitter or Facebook, the view controller is dismissed, and the app continues working. However, when I email or text the same content, the view controller is dismissed but the app freezes (not crashes). Everything is still on screen but frozen - no input etc.
Perhaps the Mail or Message apps have not released control back to my app? Is there a way using Instruments to analyze what's going on?
Thanks!
I am getting a leak from this part from NSArray as the offenders
- (void)postToFacebook:(UITapGestureRecognizer *)sender
{
NSString *postText = #"Testing";
UIImage *imageToPost = [self captureTheScreenImage];
NSArray *postItems = #[postText, imageToPost];
UIActivityViewController *activityPostVC = [[UIActivityViewController alloc]initWithActivityItems:postItems applicationActivities:nil];
NSArray *excludedItems = #[UIActivityTypePostToWeibo,UIActivityTypePrint,UIActivityTypeCopyToPasteboard,UIActivityTypeAssignToContact,UIActivityTypeSaveToCameraRoll, UIActivityTypeMail, UIActivityTypeMessage];
[activityPostVC setExcludedActivityTypes:excludedItems];
[self presentViewController:activityPostVC animated:YES completion:nil];
}
This issue happened to me too when using multiple UIWindow objects at the same time.
Upon dismissal of the UIActivityViewController, the presenting windows contents are not restored correctly. Specifically, the window's first subview (UILayoutContainerView) is missing its constraints to the superview. This causes the window the width of the UILayoutContainerView to be zero causing the window appear transparent and reveal the window underneath it but not allowing user interaction.
The fix can be to place an empty transparent window on top of the current window and present the UIActivityViewController from an empty view controller associated with that new window. When the UIActivityViewController is dismissed, we can dispose of the empty window that it was presented from.
import Foundation
private var previousWindow: UIWindow?
private var activityViewControllerWindow: UIWindow?
extension UIViewController {
fileprivate var isActivityViewControllerWindowPresented: Bool {
return activityViewControllerWindow?.isKeyWindow ?? false
}
func presentActivityViewController(_ activityViewController: UIActivityViewController, animated: Bool = true, completion: (() -> Void)? = nil) {
if isActivityViewControllerWindowPresented {
return
}
let window = UIWindow(frame: view.window!.frame)
previousWindow = UIApplication.shared.keyWindow
activityViewControllerWindow = window
window.rootViewController = UIViewController()
window.makeKeyAndVisible()
let activityCompletionClosure = activityViewController.completionWithItemsHandler
activityViewController.completionWithItemsHandler = { [weak self] (activityType, completed, returnedItems, activityError) in
self?.cleanUpActivityViewControllerWindow()
activityCompletionClosure?(activityType, completed, returnedItems, activityError)
}
window.rootViewController?.present(activityViewController, animated: animated, completion: completion)
}
fileprivate func cleanUpActivityViewControllerWindow() {
previousWindow?.makeKeyAndVisible()
activityViewControllerWindow?.rootViewController = nil
activityViewControllerWindow = nil
}
}
Yes, there is a way, like you mentioned, using Instruments. But If I were to foreshadow your results, I'd say you might want to do network calls on a non-UI thread, somewhere in the background so that your UI thread can do its thang while your app talks to Twitter or Facebook.
I use to develop an application MonoTouch Iphone, but I have a problem using UIPopoverController. I can not open the page to select the photo.
I use the class of camera.cs TweetStation.
Here's the code:
public static void SelectPicture (UIViewController parent, Action<NSDictionary> callback)
{
if(OzytisUtils.isIpad()){
picker = new UIImagePickerController();
UIPopoverController popover = new UIPopoverController(picker);
picker.Delegate = new CameraDelegate();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
popover.SetPopoverContentSize(new SizeF(parent.View.Frame.Width,parent.View.Frame.Height),true);
if(popover.PopoverVisible){
popover.Dismiss(true);
picker.Dispose();
popover.Dispose();
}else{
popover.PresentFromRect(parent.View.Frame,parent.View,UIPopoverArrowDirection.Right,true);
}
}else{
Init ();
picker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
_callback = callback;
parent.PresentModalViewController (picker, true);
}
Thanks for your help.
I have a few suggestions. First make the UIPopoverController a member variable so that it does not get collected.
Second I called ContentSizeForViewInPopover on the picker.
picker.ContentSizeForViewInPopover = new SizeF(this.View.Frame.Width,this.View.Frame.Height);
Finally I use a 0x0 rectangle in the upper left of the screen for the PresentFromRect call.
_popover.PresentFromRect(new RectangleF (0,0,0,0),this.View,UIPopoverArrowDirection.Up,true);
What I want to do is create a Popup with a UIImagePickerController. This parts easy but I want to create a utility method that generates the UIImagePickerController popup and returns the UIImage once the user selects it. The problem is that the UIImagePickerController has a delegate property that is used for asynchronous completion. My thought was that maybe I could pass in a delegate to my utility function that contains the code to execute once the image is selected but the code to execute needs to operate on the image that was selected. This is the code I have so far and just so everyone knows, it crashes. I believe it's because I'm executing it in a static method.
namespace GalleryProto
{
static public class CameraUtility
{
public static void GetImageFromGalleryWithPopup(UIViewController parentViewController, PointF centerPoint)
{
UIImagePickerController imagePicker;
UIPopoverController popOver;
imagePicker = new UIImagePickerController ();
popOver = new UIPopoverController (imagePicker);
popOver.DidDismiss += (popOverController, e) =>
{
if (popOver != null && popOver.PopoverVisible) {
Console.WriteLine ("Popover Dismissed.");
popOver.Dismiss (true);
imagePicker.Dispose ();
popOver.Dispose ();
imagePicker = null;
popOver = null;
}
};
Console.WriteLine ("Before Finished Picking Image Delegate.");
imagePicker.Delegate = new MyPickerDelegate (imagePicker, popOver);
imagePicker.SourceType = UIImagePickerControllerSourceType.PhotoLibrary;
imagePicker.AllowsEditing = false;
imagePicker.MediaTypes = new string[] {"public.image"};
RectangleF popRectangle = new RectangleF (centerPoint, new SizeF (1, 1));
popOver.PresentFromRect (popRectangle, parentViewController.View, 0, true); //Center the popup on the Image Content View.
}
public class MyPickerDelegate : UIImagePickerControllerDelegate
{
UIImagePickerController _imagePicker;
UIPopoverController _popOver;
public MyPickerDelegate(UIImagePickerController imagePicker, UIPopoverController popOver)
{
_imagePicker = imagePicker;
_popOver = popOver;
}
public override void Canceled (UIImagePickerController picker)
{
Console.WriteLine("Canceleled");
}
public override void FinishedPickingImage (UIImagePickerController picker, UIImage image, NSDictionary editingInfo)
{
Console.WriteLine("Finished Picking Image");
_popOver.Dismiss (true);
_imagePicker.Dispose ();
_popOver.Dispose ();
_imagePicker = null;
_popOver = null;
}
}
}
}
I solved my issue by passing in a delegate for the callback that takes a UIImage as an argument so that the image can be manipulated appropriately once it's selected.