Adding Multiple Images to UIPageViewController using Camera - swift

new to app development.
What I want to do is allow the user to take 2 photos from the Camera and review them. Will then code around those two photos. Preference is just to take from Camera but also could be from PhotoLibrary.
My thought process around this was, click a button to take first photo, then display that photo in a UIPageViewController then allow them to take another photo by pushing a Button to call the camera again, then that Photo would be the second as part of the UIPageViewController. So the user can swipe with the 2 dots at the bottom to review, then select confirm.
So what I did was follow the UIImagePickerController to be able to allow the user to select from library or Camera and also implemented the UIPageViewController. But having trouble putting the two together.
When I get an image from Camera I think I need to save it. The pageviewcontroller gets the filenames from a NSMutableArray.
With the viewDidLoad... I call the Camera, and also the pageViewController. My problem is the pageviewcontroller gets data from pageImages[] but how do I populate the array with the image name string? Did I do it correctly below? I think i may have a problem in that does the pageviewcontroller controller automatically update once i dismiss the picker otherwise will never get the image from the camera. I am thinking i may have to call camera then pass the data to the view controller through segues?
Other thought I was having was to have a view controller that just displays the taken image, saves it, adds filename to array, then when user takes another photo, the image is saved and added to array and then passed through segue to the pageviewcontroller for the user to be able to scroll through both and either confirm or delete. Thoughts?
override func viewDidLoad() {
super.viewDidLoad()
createPhoto()
self.pageViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PhotoPageViewControllerStoryBoard") as! UIPageViewController
self.pageViewController.dataSource = self
var startVc = self.pageViewControllerAtIndex(0) as PhotoContentPageViewController
var viewControllers = NSArray(object: startVc)
self.pageViewController.setViewControllers(viewControllers as? [UIViewController], direction: .Forward, animated: true, completion: nil)
self.pageViewController.view.frame = CGRectMake(0, -10, self.view.frame.size.width, self.view.frame.size.height-30)
self.addChildViewController(self.pageViewController)
self.view.addSubview(self.pageViewController.view)
self.pageViewController.didMoveToParentViewController(self)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
var newImage: UIImage
if let possibleImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
newImage = possibleImage
} else if let possibleImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
newImage = possibleImage
} else {
return
}
if (picker.sourceType == UIImagePickerControllerSourceType.Camera) {
let imageName = NSUUID().UUIDString
let imagePath = getDocumentsDirectory().stringByAppendingPathComponent(imageName)
if let jpegData = UIImageJPEGRepresentation(newImage, 80) {
jpegData.writeToFile(imagePath, atomically: true)
}
pageImages!.addObject(imageName)
}
dismissViewControllerAnimated(true, completion: nil)
}

ok, think I got it, i changed the array to pass to the pageviewcontroller from the contentviewcontroller UIImages instead of filename strings.
Also, since the array was nil i didn't start the pageviewcontroller until after the first image was taken or chosen.
Off to battle the next set of code, to actually do something with the two image. most likely will need some help in the future... near future. Thanks.

Related

Segued Image not showing after cancel action on camera?

I am working on a camera iOS application using swift. I'm segueing an image to a view(uploadView) and displaying it using that image on the in camera but when I press the cancel on the camera and return to the view(uploadView) the segued image disappears. Already tried storing that image in a new UIImage variable but for some reason still remains empty after pressing cancel or going back to that view(uploadView).
I'm not sure if there is any other way to store the image when the view loads for the first time and keep the image there.
//for my view where I am displaying the image
override func viewDidLoad() {
super.viewDidLoad()
uploadImage.image = firstImageInSecondView
UILabel.text = String(Int(50.0))
}
// this is what I am trying to show the image again
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
uploadImage.image = firstImageInSecondView
print(uploadImage.isHidden)
}
Output is false.

ScrollView not reloading upon dismissing ViewController and calling viewDidLoad

I have a scrollView that contains a dynamic amount of WeatherViewControllers each displaying the weather data of a different city the user has saved. The user can segue from the WeatherViewControllers to a CityListViewController. Where they can add and remove cities from their list which in turn should add and remove WeatherViewControllers from the scrollView upon dismissing the CityListViewController, this is where I am running into a problem.
Currently I am using a protocol to call viewDidLoad in the scrollViewController upon dismissing the CityListViewController which works as I follow the code with the debugger all of the code that should be getting called is and the variable which tracks how many viewControllers to create is accurate after the change, however the scrollView is not changing. If I add/remove a city from the list the scrollView still displays the same amount as before.
The func createAndAddWeatherScreen is called the accurate amount of times after the change and everything and if I close the app and reopen it the scrollView then displays the right amount of viewControllers. It seems like everything is working except the scrollView is not reloading upon dismissing the cityListController.
Side Note: Upon initially opening the app the scrollView loads properly with all the correct WeatherViewControllers in the UIScrollView and the correct cities in the list.
class ScrollViewController: UIViewController, ScrollReloadProtocol {
func reloadScrollView() {
self.viewDidLoad()
}
//#IBOutlet var totalScrollView: UIScrollView!
var pages = [ViewController]()
var x = 0
var weatherScreensArray = [SavedCityEntity]()
var weatherScreenStringArray = [String]()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var horizString = "H:|[page1(==view)]"
let defaults = UserDefaults.standard
let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.translatesAutoresizingMaskIntoConstraints = false
scrollView.isPagingEnabled = true
return scrollView
}()
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
view = scrollView
//userDefaults used to keep track of which screen is which to put different cities on different viewControllers
defaults.set(0, forKey: "screenNumber")
//load cities to get number of cities saved
loadCities()
var views : [String: UIView] = ["view": view]
//create all weatherWeatherControllers
while x <= weatherScreensArray.count {
pages.append(createAndAddWeatherScreen(number: x))
weatherScreenStringArray.append("page\(x+1)")
views["\(weatherScreenStringArray[x])"] = pages[x].view
let addToHoriz = "[\(weatherScreenStringArray[x])(==view)]"
horizString.append(addToHoriz)
x+=1
}
horizString.append("|")
let verticalConstraints = NSLayoutConstraint.constraints(withVisualFormat: "V:|[page1(==view)]|", options: [], metrics: nil, views: views)
let horizontalConstraints = NSLayoutConstraint.constraints(withVisualFormat: horizString, options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
NSLayoutConstraint.activate(verticalConstraints + horizontalConstraints)
}
//Function to create and add weatherViewController
func createAndAddWeatherScreen(number: Int) -> ViewController {
defaults.set(number, forKey: "screenNumber")
let story = UIStoryboard(name: "Main", bundle: nil)
let weatherScreen = story.instantiateViewController(identifier: "View Controller") as! ViewController
weatherScreen.view.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(weatherScreen.view)
addChild(weatherScreen)
weatherScreen.didMove(toParent: self)
return weatherScreen
}
There are several architectural issues with your code:
You should not be calling the viewDidLoad method manually, it's very easy to extract the necessary code for fetching and reloading into a method of its own which doesn't break the lifecycle of a viewController.
It looks like you're attempting to use the scrollView as a tableView which offers reloading as a free function, as well as far better memory management.
You could use .map() instead of using while to iterate and create new arrays manually, higher order functions are very useful.
Despite all that, you can't actually reload a scrollView in the way you can a tableView. It already contains the views you added there previously, and it looks to me like you're stacking extra views over and over again, you could debug if this is the case with the visual debugger.
Lastly, in order to update a view after updating constraints you need to call:
setNeedsLayout()
layoutIfNeeded()
to let the system know you want to update the subviews and their layout.

Gesture Recognizer and UIImageView With Custom Property

My app uses images which can have various statuses so I am using custom properties as tags. This works ok, but my tap gesture recognizer can't seem to access these properties. When the image is tapped, I need the action to depend on the state of these properties. Is there a way the gesture recognizer can read these custom properties from the tapped subclassed UIImageView or should I take a different approach? Thanks!
public class advancedTagUIImageView: UIImageView {
var photoViewedStatus: Bool?
var photoLikedStatus: Bool?
}
viewDidLoad() {
let imageView = advancedTagUIImageView(frame:CGRect(origin: CGPoint(x:50, y:50), size: CGSize(width:100,height:100)))
imageView.image = UIImage(named: dog.png)
imageView.photoViewedStatus = false
imageView.photoLikeStatus = false
imageView.tag = 7
imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(soundTapped)))
view.addSubview(imageView)
}
#objc func soundTapped(gesture: UIGestureRecognizer) {
let photoTag = gesture.view!.tag // this works great
let isPhotoLiked = gesture.view!.photoLikeStatus // this doesn't work
// do whatever
}
Swift is strongly typed. The type of the gesture.view property is UIView which doesn't have the properties defined in your advancedTagUIImageView class. This is because you could theoretically also attach your UITapGestureRecognizer to any other type of view. In which case the program would crash on your soundTapped method, because you're just assuming that gesture.view is an advancedTagUIImageView which might not always be the case.
For the compiler to let you access these properties you need first check if gesture.view is really your sublcass like this:
if let photoView = (gesture.view? as? advancedTagUIImageView) {
// you can access your tags here
let isPhotoLiked = photoView.photoLikeStatus
} else {
// you might want to handle the case that the gesture was invoked from another view. If you're certain this should not happen, maybe just throw an assertion error to get notified in case it still does.
}
PS: According to the Swift API Design Guidelines type names should be capitalized, so in your case it should be AdvancedTagUIImageView. Not following these guidelines might not crash your program, but doing so might make your life a lot easier should you ever need to write code together with other people.

Is it possible to create an image of a full view controller (not just what's on screen at once)?

This is the code I'm using at the moment:
func configureMailController() -> MFMailComposeViewController {
UIGraphicsBeginImageContext(self.view.bounds.size);
self.view.layer.render(in: UIGraphicsGetCurrentContext()!)
let screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setToRecipients(["emailaddress#email.com"])
mailComposerVC.setSubject("Form")
mailComposerVC.setMessageBody("Here's the image", isHTML: true)
let imageData: NSData = UIImagePNGRepresentation(screenShot!)! as NSData
mailComposerVC.addAttachmentData(imageData as Data, mimeType: "image/png", fileName: "imageName")
return mailComposerVC
}
This code emails an image of screenShot, however I need it to be a much larger image showing the whole viewController not just the bottom part which is on screen when the action is activated.
Is it possible to adjust this code to do so, I tried changing the code from using my UIView to my UIScrollView however that didn't work either, in fact that didn't even produce an image.
UPDATE: I've attached a video to show what is currently happening.
I tested your code on one of my projects and the whole view controller was captured just fine, the only thing that was missing was my navigation bar from the navigation controller.
If you want to capture the navigationBar or tabBar you need to make sure you choose the correct view to render, here is an example that checks if the navigationController exists and grabs the navigationController's view
func configureMailController() -> MFMailComposeViewController {
let nav = self.navigationController!
UIGraphicsBeginImageContext(nav.view.bounds.size);
nav.view.layer.render(in: UIGraphicsGetCurrentContext()!)
let screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setToRecipients(["emailaddress#email.com"])
mailComposerVC.setSubject("Form")
mailComposerVC.setMessageBody("Here's the image", isHTML: true)
let imageData: NSData = UIImagePNGRepresentation(screenShot!)! as NSData
mailComposerVC.addAttachmentData(imageData as Data, mimeType: "image/png", fileName: "imageName")
return mailComposerVC
}
Hope that helps, would still like to see the screenshots you have ended up with so that I can be sure of your problem.
Update: Now you have added the video I can see that you are using a scrollView. I've not tested this but you'll want to start by telling the graphics context to use the contentSize from the scrollView
What I'm not sure about is wether this will capture all of your scrollViews content as we are still using the navigationControllers view to render.
You may have to tell you scrollView not to clip it's contents and also send the scrollView back to the top like this:
...
self.scrollView.clipsToBounds = false
self.scrollView.setContentOffset(CGPoint(x:0, y:0), animated: false)
let nav = self.navigationController!
UIGraphicsBeginImageContext(self.scrollView.contentSize);
nav.view.layer.render(in: UIGraphicsGetCurrentContext()!)
let screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
...
Let me know how you get on.
Following the answer from #Wez:
Using the code below creates an image of the whole scroll view.
...
self.scrollView.clipsToBounds = false
self.scrollView.setContentOffset(CGPoint(x:0, y:0), animated: false)
let scroll = self.scrollView!
UIGraphicsBeginImageContext(self.scrollView.contentSize);
scroll.layer.render(in: UIGraphicsGetCurrentContext()!)
let screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
...
However, this doesn't include the navigation bar. For my purpose it's not necessary to have the navigation bar but if someone does find a way to include it that would be great.

How do i get rid of xib after presenting it?

I am creating my own UIImagePicker Overlay and am using a Xib in order to display the overlay. Whenever I present the imagepicker, it seems to present twice, once when I call the main bundle to load the nib and the other when I explicitly present the imagepickercontroller. The main problem i'm having is when I dismiss the imagepickercontroller, the first one goes, which is great, but the underlying view of the xib still remains and doesn't go regardless of what i do.
I tried to convert the Objective-C example project for how to create a custom uiimagepickercontroller Apple has on their website into Swift. I'm not sure what I"m doing wrong, here is my code:
func showImagePickerForSourceType(sourceType: UIImagePickerControllerSourceType) {
let picker = UIImagePickerController()
picker.modalPresentationStyle = UIModalPresentationStyle.CurrentContext
picker.sourceType = sourceType
picker.delegate = self
if sourceType == UIImagePickerControllerSourceType.Camera {
//gets rid of the out of the box imagepicker
picker.showsCameraControls = false
//loads the overlay xib for the camera
NSBundle.mainBundle().loadNibNamed("OverlayView", owner: self, options: nil)
self.overlayView.frame = (picker.cameraOverlayView?.frame)!
picker.cameraOverlayView = self.overlayView
self.overlayView = nil
//makes the image capture screen the full screen size
let screenSize: CGSize = UIScreen.mainScreen().bounds.size
let cameraAspectRatio: Float = 4.0 / 3.0
let imageWidth:Float = floorf(Float(screenSize.width) * cameraAspectRatio)
let scale: Float = ceilf((Float(screenSize.height + 120) / imageWidth) * 10.0) / 10.0
picker.cameraViewTransform = CGAffineTransformMakeScale(CGFloat(scale), CGFloat(scale))
}
self.imagePickerController = picker
self.presentViewController(self.imagePickerController, animated: true, completion: nil)
}
Here is the code for a button on the xib dismissing the view. How can i fix this? Because i'm printing out the array of view controllers on the navigation stack, and it only shows one.
#IBAction func goBack(sender: AnyObject) {
self.imagePickerController.dismissViewControllerAnimated(true, completion: nil)
}
It sounds like you have your view controller's view outlet connected to the view in your .xib file in addition to the overlayView outlet. This would probably cause what you're describing.