In my app, I want the user to be able to be able to take a picture, be presented with the picture, and by tapping the photo a textfield can be added so that they can write on top of the image. This is exactly the same as the functionality of adding text to pictures in Snapchat.
As far as I can understand, the only way to be presented the image after having taken it and be able to edit it, is to set:
imagePicker.showsCameraControls = false
Make a custom overlay:
#IBAction func takePhoto(sender: UIButton) {
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .Camera
imagePicker.showsCameraControls = false
imagePicker.allowsEditing = true
let overlayView = UIView(frame: CGRectMake(0, self.view.frame.width, self.view.frame.width, self.view.frame.height-self.view.frame.width))
overlayView.backgroundColor = UIColor.blackColor()
overlayView.alpha = 0.5
println(overlayView)
let snapButton = UIView(frame: CGRectMake(150, 160, 80, 80))
snapButton.layer.cornerRadius = 40
snapButton.userInteractionEnabled = true
snapButton.backgroundColor = UIColor.purpleColor()
overlayView.addSubview(snapButton)
overlayView.bringSubviewToFront(snapButton)
let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleSnapTap:"))
recognizer.delegate = self
snapButton.addGestureRecognizer(recognizer)
let cancelButton = UIView(frame: CGRectMake(40, 40, 44, 44))
cancelButton.layer.cornerRadius = 22
cancelButton.userInteractionEnabled = true
cancelButton.backgroundColor = UIColor.redColor()
overlayView.addSubview(cancelButton)
overlayView.bringSubviewToFront(cancelButton)
let cancelRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleCancelTap:"))
cancelRecognizer.delegate = self
cancelButton.addGestureRecognizer(cancelRecognizer)
let changeCameraButton = UIView(frame: CGRectMake(165, 40, 44, 44))
changeCameraButton.layer.cornerRadius = 22
changeCameraButton.userInteractionEnabled = true
changeCameraButton.backgroundColor = UIColor.blueColor()
overlayView.addSubview(changeCameraButton)
overlayView.bringSubviewToFront(changeCameraButton)
let changeCameraRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleChangeCameraTap:"))
changeCameraRecognizer.delegate = self
changeCameraButton.addGestureRecognizer(changeCameraRecognizer)
let flashButton = UIView(frame: CGRectMake(300, 40, 44, 44))
flashButton.layer.cornerRadius = 22
flashButton.userInteractionEnabled = true
flashButton.backgroundColor = UIColor.yellowColor()
overlayView.addSubview(flashButton)
overlayView.bringSubviewToFront(flashButton)
let flashRecognizer = UITapGestureRecognizer(target: self, action:Selector("handleFlashTap:"))
flashRecognizer.delegate = self
flashButton.addGestureRecognizer(flashRecognizer)
imagePicker.cameraOverlayView = overlayView
presentViewController(imagePicker, animated: true, completion: nil)
}
func handleSnapTap(recognizer: UITapGestureRecognizer) {
println("Take picture")
imagePicker.takePicture()
self.performSegueWithIdentifier("cameraToImageViewSegue", sender: self)
}
func handleCancelTap(recognizer: UITapGestureRecognizer) {
println("Cancel")
self.imagePicker.dismissViewControllerAnimated(true, completion: nil)
}
func handleChangeCameraTap(recognizer: UITapGestureRecognizer) {
if (hasChangedCamera == nil){
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.Front
hasChangedCamera = true
return
}
if (hasChangedCamera == true){
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.Rear
hasChangedCamera = false
return
}
if (hasChangedCamera! == false){
imagePicker.cameraDevice = UIImagePickerControllerCameraDevice.Front
hasChangedCamera = true
return
}
}
func handleFlashTap(recognizer: UITapGestureRecognizer) {
if (hasTurnedOnFlash == nil){
imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashMode.On
hasTurnedOnFlash = true
return
}
if (hasTurnedOnFlash == true){
imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashMode.Off
hasTurnedOnFlash = false
return
}
if (hasTurnedOnFlash == false){
imagePicker.cameraFlashMode = UIImagePickerControllerCameraFlashMode.On
hasTurnedOnFlash = true
return
}
}
And finally present a new view controller in which the picked image is placed in a UIView, and edit it from there. My issue is how to segue directly from the UIImagePickerController to a new view controller. I have tried the following:
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
self.imagePicker.dismissViewControllerAnimated(false, completion: nil)
let vc = ModifyCameraImageViewController() //change this to your class name
self.presentViewController(vc, animated: false, completion: nil)
}
First off, this just leads to a black screen, but I'm sure there's a simple enough way around that. My main issue is the fact that the view controller from which the UIImagePickerController was presented briefly appears on the screen before the next view controller appears. This obviously does not look good. I also tried removing the dismissViewController function, as well as placing the presentViewController function above the dismissView controller function. Both of these attempts gave me the error message:
Warning: Attempt to present <xxx.ModifyCameraImageViewController: 0x145e3eb70> on <xxx.ViewController: 0x145d20a60> whose view is not in the window hierarchy!
Attempting to use performSegueWithIdentifier with a segue linking the underlying view and the next view controller gives the same error warning.
I have found the following similar question, but I am completely inept at Objective C, so I'm struggling to make any sense of it: Push a viewController from the UIImagePickerController camera view
So, can anyone help in regards to how to present a view controller directly from the UIImagePickerController?
Also, keep in mind that I'm doing this in order to be able to create a text overlay on the newly picked image (like in Snapchat), so if anyone has a more elegant solution to that, feel free to post it!
Thanks!
Ok, found a simple solution to my issue. Instead of presenting the imagePickerController from the underlying view controller when the takePicture button is pressed, and segueing to another view controller directly from there, use the takePicture button to segue to another view controller and present the imagePickerController from the viewDidLoad of the second view controller. The second view controller will then be presented when the imagePickerController is dismissed. This however requires the underlying view controller to look similar to the camera controls, and some playing around with animations for this to look natural.
let pickerController: Void = picker.dismissViewControllerAnimated(true) { _ in
UIImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
self.performSegueWithIdentifier("segueIdentifier", sender: nil)
//Do what you want when picker is dismissed
}
Related
I have a View Controller embedded in Navigation Controller. The view has 1 WKWebView, hence, I'm setting view = webView in loadView() override.
So, I'm adding a small little sub navigation bar underneath my navigation controller to allow a user to change their location.I can add the subview to the navigation controller, I'm just not able to make it clickable.
override func loadView() {
let config = WKWebViewConfiguration()
config.processPool = YourModelObject.sharedInstance.processPool
webView = WKWebView(frame: .zero, configuration: config)
webView.navigationDelegate = self
self.webView.scrollView.delegate = self
view = webView
..
if let navigationBar = self.navigationController?.navigationBar {
let secondFrame = CGRect(x: 50, y: 44.1, width: navigationBar.frame.width, height: 30)
let secondLabel = UILabel(frame: secondFrame)
secondLabel.textColor = .black
secondLabel.text = "Getting your location..."
secondLabel.isUserInteractionEnabled = true
let guestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(setLocation(_:)))
secondLabel.addGestureRecognizer(guestureRecognizer)
secondLabel.textAlignment = .left
secondLabel.font = secondLabel.font.withSize(14)
secondLabel.tag = 1002
navigationBar.addSubview(secondLabel)
}
}
And then the setLocation function
#objc func setLocation(_ sender: Any) {
print("location label tapped")
}
But when I tap the label, I'm not getting anything printed in console. I don't know if the use of target: self is wrong for the tapGestureRecognizer or what's going on here.
I too am new to Swift, so my answer is far from guaranteed. I just know what it's like to be in your position,
Perhaps try creating a subclass of navigationBar for the sub navigation bar, i.e. mySubNavigationBar. Then in the subclass's code do all the initialization that you need to do. Including the print line so you'll know if you're getting there.
p.s. I would have put this as a comment, but I don't have enough points to add comments.
How can I implement the fullscreen image and dismiss image functionality of the Apple Photos? Additionally, how can I get the aspect ratio of an image to fit within a fullscreen view? Are they sending a UIImage to a new view controller?
My current method of fullscreening an image simply sets a UIImageView's frame equal to the superview's frame while turning the alphas of the UINavigationBar and UITabBar to 0. And to dismiss, I added a tap gesture recognizer that reverses the alphas and removes the UIImageView from the superview.
Here's my fullscreen and dismiss code
func fullscreen(forImage image: UIImage) {
let imageView = UIImageView(image: image)
imageView.frame = self.view.frame
imageView.backgroundColor = .black
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.isUserInteractionEnabled = true
self.navigationController?.navigationBar.alpha = 0
self.tabBarController?.tabBar.alpha = 0
let dismissTap = UITapGestureRecognizer(target: self, action: #selector(dismissFullscreenImage))
imageView.addGestureRecognizer(dismissTap)
self.view.addSubview(imageView)
}
#objc func dismissFullscreenImage(_ sender: UITapGestureRecognizer) {
sender.view?.removeFromSuperview()
self.navigationController?.navigationBar.alpha = 1
self.tabBarController?.tabBar.alpha = 1
}
This could easily be achieved using Apple's QuickLook framework as it works great for many extensions and collections of images.
Edit: Most of the functionality you want is built into the QLPreviewController
let previewController = QLPreviewController()
previewController.dataSource = self
self.present(previewController, animated: true, completion: nil)
The data source is whatever class conforms to the QLPreviewControllerDataSource protocol
Here is a video guide from apple on how to achieve this easily
Edit: This part goes in the previewItemAt function
guard let url = Bundle.main.url(forResource: "imageName", withExtension: "jpg")
else {
fatalError("Could not load imageName.jpg")
}
return url as QLPreviewItem
I am working on an app with a popover that displays a UIDatePicker. I have it set up to always be a popup view, even on phones. It works until I rotate the device to landscape. When I do this, the popover displays the way a popover would normally display on an iPhone that is in landscape mode.
This is what it looks like in portrait mode. It's exactly what I wanted it to look like.
And here it is in landscape mode:
It doesn't matter if I turn the device when the popup is already visible or if I pop it up after the device is turned. This is the code that I am using to display the popup.
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
#IBAction func showPopUpDate(_ sender: UIButton) {
let popoverVC = storyboard?.instantiateViewController(withIdentifier: "DateTimeSelectorView") as! DateVC
popoverVC.modalPresentationStyle = .popover
popoverVC.preferredContentSize = CGSize(width: 350, height: 350)
popoverVC.labelTag = sender.tag
if sender.tag == 0 {
popoverVC.showType = .date
popoverVC.passedDate = date
}
else {
popoverVC.showType = .time
popoverVC.passedDate = time
}
if let popoverController = popoverVC.popoverPresentationController {
popoverController.permittedArrowDirections = .any
popoverController.sourceRect = CGRect(x: 0, y: 0, width: 20, height: 20)
popoverController.delegate = self
popoverController.sourceView = sender
popoverVC.delegate = self
present(popoverVC, animated: true, completion: nil)
}
}
How can I set this up so that the popover continues to look like it does in portrait mode?
Please add vertically center and horizontally center constrain to the view.
I have a view (UIPopoverPresentation) which functionality works fine, but I need to add a custom border. I'm currently using a borderWidth and borderColor but I cannot seem to find a way to make a customized border, as seen in the photo below. How do I go about creating this customized border? Make a CGRect?
What I need:
What I have:
I've attempted to add an image to the background of the popover and it resulted in this:
EDIT: //PopOverView (presented using UIPopOverPresentation)
override func viewDidLoad() {
super.viewDidLoad()
self.view.layer.cornerRadius = 10.0
self.view.layer.borderWidth = 1.5
self.view.layer.borderColor = UIColor.whiteColor().CGColor
self.navigationController?.navigationBarHidden = true
self.popViewTableView.delegate = self
self.popViewTableView.dataSource = self
self.popViewTableView.alwaysBounceVertical = false
self.popViewTableView.backgroundColor = UIColor(red: 151.0/255.0, green: 87.0/255.0, blue: 172.0/255.0, alpha: 1.0)
}
//Base View Controller. When button is pressed, this function is called which presents the popover
func presentPopOver() {
let contentView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("popViewController") as! DeckPopViewController
contentView.modalPresentationStyle = UIModalPresentationStyle.Popover
contentView.preferredContentSize = CGSizeMake(deckSelectionCGRect.width, 160)
let popoverMenuViewController = contentView.popoverPresentationController!
popoverMenuViewController.delegate = self
popoverMenuViewController.sourceView = view
popoverMenuViewController.permittedArrowDirections = UIPopoverArrowDirection(rawValue:0)
popoverMenuViewController.sourceRect = CGRectMake((self.view.bounds.width/2) - (deckSelectionCGRect.width/2), 120, deckSelectionCGRect.width, deckSelectionCGRect.height)
presentViewController(contentView, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
//ALSO: On the third image, I placed the border imageview over the entire view controller in the storyboard, set padding to zero on all sides (autolayout). It doesn't look good, though.
Is anyone aware of an issue with stopping a UIActivityIndicatorView (AI) / removing a UIView that has been added to a UIPageViewController?
I am showing the AI using:
override func viewWillLayoutSubviews() {
actInd.frame = CGRectMake(0,0, 80, 80)
actInd.center.x = self.view.center.x
actInd.center.y = self.view.center.y - 40
actInd.hidesWhenStopped = true
actInd.layer.cornerRadius = 5
actInd.alpha = 0.5
actInd.backgroundColor = UIColor.blackColor()
actInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.WhiteLarge
self.view.addSubview(actInd)
actInd.startAnimating()
// This next line prints out the details of each subview on the page for testing purposes
listSubviews(self.view)
I am also, for testing purposes, adding a blank UIView to the view using:
aView.frame = CGRectMake(0, 0, 200, 200)
aView.backgroundColor = UIColor.blueColor()
self.view.addSubview(aView)
}
Then I send my Parse.com query using a delegate to advise the UIPageViewController (e.g. this view) when the data is ready. This is working fine and I have tested that the data returns correctly, and that the expected method is being called.
In this method I try to stop the UIActivityViewController & try to hide aView:
self.actInd.stopAnimating()
aView.removeFromSuperView()
This didn't work so I tried:
self.actInd.removeFromSuperView()
This didn't work either. So I then tried to search through the subviews on the current view using:
func populateBoards(boards: [Board]) {
println(self.actInd) // PRINT LINE 1
var subviews = self.view.subviews
for v in subviews {
if v.isKindOfClass(UIActivityIndicatorView) {
println("YES, it is an activity view indicator \(v)") // PRINT LINE 2
v.stopAnimating()
v.removeFromSuperview()
}
}
self.boards = boards
println("Populated Boards!") // PRINT LINE 3
}
PRINT LINE 1 outputs: >
PRINT LINE 2 outputs: YES, it is an activity view indicator >
PRINT LINE 3 outputs: "Populated boards!"
EDIT:
The UIPageViewController is setup with the following code (in case it helps):
func setupUIPageView() {
self.pageViewController = UIPageViewController(transitionStyle: UIPageViewControllerTransitionStyle.Scroll, navigationOrientation: UIPageViewControllerNavigationOrientation.Horizontal, options: nil)
self.pageViewController.delegate = self
self.pageViewController.dataSource = self
var startVC = self.viewControllerAtIndex(0) as ViewController
var viewControllers:[ViewController] = [startVC]
self.pageViewController.setViewControllers(viewControllers, direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.width, self.view.frame.height)
self.pageViewController.view.alpha = 0.0
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
Help! Thanks all in advance :D
You should call all your UI related code on the main queue. Your Parse query is async and the completion block might be called on a different thread.
Wrap this call in this block:
NSOperationQueue.mainQueue().addOperationWithBlock {
self.actInd.stopAnimating()
}