Capture visible portion of UIScrollView and subview - iphone

I have zoomable image in a UIScrollview. Once the image is scrolled to the user's liking, the scrollview is locked. A UITextField can then be edited, and is (currently) added to the UIImageView. The context of the image is captured and saved.
Without scrolling, this works fine, however, the context is based on the frame of the UIImageView. How can it capture the visible portion of the screen instead, leaving out the navigation bar and toolbar? The function used to save the image is below.
#IBAction func saveButtonPressed(sender: AnyObject) {
let newImage = imageStore.createImage(textLabelBottom.text, imageName: "image1") { () -> UIImage in
// Can't add the textLabel to imageView because its size changes
self.imageView.addSubview(self.textLabelBottom)
UIGraphicsBeginImageContextWithOptions(self.imageView.frame.size, false, 0.0)
let ctx = UIGraphicsGetCurrentContext()
self.imageView.layer.renderInContext(ctx)
self.imageToSave = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return self.imageToSave
}
imageStore.saveImage(newImage)
self.showActivityViewController()
}
I wan't to save the visible portion of the image based on its position in the scrollview with the textLabel in a static spot. The code currently adds textLabel to the same point in the image regardless of zoom, with the saved image either very small or very large based on the UIScrollView.
Update: I changed the code to the following:
#IBAction func saveButtonPressed(sender: AnyObject) {
let newImage = imageStore.createImage(textLabelBottom.text, imageName: "image1") { () -> UIImage in
// Can't add the textLabel to imageView because its size changes
self.imageView.addSubview(self.textLabelBottom)
UIGraphicsBeginImageContextWithOptions(self.imageScrollView.bounds.size, false, UIScreen.mainScreen().scale)
let ctx = UIGraphicsGetCurrentContext()
let offset: CGPoint = self.imageScrollView.contentOffset
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -offset.x, -offset.y)
self.imageScrollView.layer.renderInContext(ctx)
self.imageToSave = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return self.imageToSave
}
imageStore.saveImage(newImage)
self.showActivityViewController()
}
This works properly, but the text field is obviously out of place. Do I add it to a specific CGPoint within the context?
Update 2:
It's my understanding that CGContextTranslateCTM changes the coordinate system to that of the layer you're working on. In this case, the new {0, 0} would be at the offset of the scrollview. When I want to place my textfield into the context, though, it doesn't show up in the right place (offscreen). Trying something like this:
#IBAction func saveButtonPressed(sender: AnyObject) {
let newImage = imageStore.createImage(textLabelBottom.text, imageName: "image1") { () -> UIImage in
// Can't add the textLabel to imageView because its size changes
self.imageView.addSubview(self.textLabelBottom)
UIGraphicsBeginImageContextWithOptions(self.imageScrollView.bounds.size, false, UIScreen.mainScreen().scale)
let ctx = UIGraphicsGetCurrentContext()
let offset: CGPoint = self.imageScrollView.contentOffset
CGContextTranslateCTM(UIGraphicsGetCurrentContext(), -offset.x, -offset.y)
self.imageScrollView.layer.renderInContext(ctx)
//Move to the correct point and render
CGContextMoveToPoint(ctx, /*point1*/, /*point2*/)
self.textLabelBottom.layer.renderInContext(ctx)
self.imageToSave = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return self.imageToSave
}
imageStore.saveImage(newImage)
self.showActivityViewController()
}
What else do I need to do to the context to do this properly?

Related

Taking a snapshot of UIImageView instead of main View

I have in my app in my View a Button for taking snapshot with below code
open func takeScreenshot(_ shouldSave: Bool = true) -> UIImage? {
var screenshotImage :UIImage?
let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
guard let context = UIGraphicsGetCurrentContext() else {return nil}
layer.render(in:context)
screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
if let image = screenshotImage, shouldSave {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
return screenshotImage
}
But in the same View is UIImageView and I want to take a snapshot of this UIImageView. How should I change the code? When I take a snapshot with this code, it will save only black screen with button for play/pause video and with button for taking snapshot.
Instead you using
let layer = UIApplication.shared.keyWindow!.layer
Just use your imageview Layer
let layer = YourImageView.layer

How to capture image of entire view's contents when bigger than screen

I need my app to render everything that a view controller has the potential to display (including off-screen content) except for the top and bottom navigation bars.
The first image, below, shows the view controller at runtime. The action menu triggers the following code which is adapted the code sample from the answer given here :
#IBAction func actionMenu(_ sender: Any) {
let activityItems = [generateImageOfTableView(tblview: tourneyEntrants)]
let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
activityController.popoverPresentationController?.sourceView = self.view
activityController.popoverPresentationController?.sourceRect = self.view.frame
self.present(activityController, animated: true, completion: nil)
}
func generateImageOfTableView(tblview: UITableView) -> UIImage {
var image = UIImage()
UIGraphicsBeginImageContextWithOptions(tblview.contentSize, false, UIScreen.main.scale)
// save initial values
let savedContentOffset = tblview.contentOffset;
let savedFrame = tblview.frame;
let savedBackgroundColor = tblview.backgroundColor
// reset offset to top left point
tblview.contentOffset = CGPoint(x: 0, y: 0);
// set frame to content size
tblview.frame = CGRect(x: 0, y: 0, width: tblview.contentSize.width, height: tblview.contentSize.height);
// remove background
tblview.backgroundColor = UIColor.clear
// make temp view with scroll view content size
// a workaround for issue when image on ipad was drawn incorrectly
let tempView = UIView(frame: CGRect(x: 0, y: 0, width: tblview.contentSize.width, height: tblview.contentSize.height))
// save superview
let tempSuperView = tblview.superview
// remove scrollView from old superview
tblview.removeFromSuperview()
// and add to tempView
tempView.addSubview(tblview)
// render view
// drawViewHierarchyInRect not working correctly
tempView.layer.render(in: UIGraphicsGetCurrentContext()!)
// and get image
image = UIGraphicsGetImageFromCurrentImageContext()!
// and return everything back
tempView.subviews[0].removeFromSuperview()
tempSuperView?.addSubview(tblview)
// restore saved settings
tblview.contentOffset = savedContentOffset;
tblview.frame = savedFrame;
tblview.backgroundColor = savedBackgroundColor
UIGraphicsEndImageContext()
return image
}
The second image, below, shows the image captured from this code.
There are two problems with it.
The first is that it is ignoring the text field and label above the table. I know that the code doesn't look for this, so I am looking for some guidance on how to capture the superview's contents (minus the navigation bars).
Second, the table view contains 18 columns of numbers but these aren't captured. So, the code copes with the height of the table being beyond the screen but not with the width. I've looked at whether auto layout maybe causing this, but cannot see anything obvious.

NSView dataWithPDF inside rect but with background view

I wan´t to take a screenshot of a NSView but when I do this, I get an image without the background. All the subviews are show, but not the background. I use this code:
if let image = NSImage(data: background.dataWithPDF(inside: background.bounds)) {
let imageView = NSImageView(image: image)
imageView.imageScaling = .scaleProportionallyUpOrDown
return NSImageView(image: image)
}
I thought, ok when I get only the subviews, then I will make a screenshot of the superview and I thried the following:
if let superview = background.superview, let image = NSImage(data: superview.dataWithPDF(inside: background.frame)) {
let imageView = NSImageView(image: image)
imageView.imageScaling = .scaleProportionallyUpOrDown
return NSImageView(image: image)
}
But I get the same result. Even if I set the background color of my background view I don´t get an image without transparent background.
How can I resolve this?
thank you
Artur
I got an Answer:
background.lockFocus()
if let rep = NSBitmapImageRep(focusedViewRect: background.bounds){
let img = NSImage(size: background.bounds.size)
img.addRepresentation(rep)
return NSImageView(image: img)
}
background.unlockFocus()
You could have converted the views backing layer.backgroundColor to an image. Then assign the image to a backing image view that sits in the 0th index of your views heirarchy and use the method dataWithPDF -> that will successfully capture a background.

Image Cropping grabbing the wrong portion of UIImage during crop

I've been working on making a view controller that will crop an image down to a specific size with some draggable control points and the background image outside of the crop zone dimmed.
For some reason whenever the image is cropped, it is grabbing the wrong reference. I've looked at just about every other post on this to deal with cropping.
Here is my setup for the Storyboard:
I've asked a few other people including a tutor and mentor from a course that I'm taking, but we all seem to be stumped.
I can select a frame by dragging the UL UR DL DR corners around the view controller like this:
But when I press the button and use the crop function I've written, I get something that is not the correct crop based on the framed selection.
I also get this error message during the cropping proceedure:
2016-09-07 23:36:38.962 ImageCropView[33133:1056024]
<UIView: 0x7f9cfa42c730; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x7f9cfa408400>>'s window
is not equal to <ImageCropView.CroppedImageViewController: 0x7f9cfa43f9b0>'s view's window!
The offending part of the code must be somewhere in one of the functions below.
Here is the cropping function:
func cropImage(image: UIImage, toRect rect: CGRect) -> UIImage {
func rad(deg: CGFloat) -> CGFloat {
return deg / 180.0 * CGFloat(M_PI)
}
// determine the orientation of the image and apply a transformation to the crop rectangle to shift it to the correct position
var rectTransform: CGAffineTransform
switch image.imageOrientation {
case .Left:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(90)), 0, -image.size.height)
case .Right:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-90)), -image.size.width, 0)
case .Down:
rectTransform = CGAffineTransformTranslate(CGAffineTransformMakeRotation(rad(-180)), -image.size.width, -image.size.height)
default:
rectTransform = CGAffineTransformIdentity
}
// adjust the transformation scale based on the image scale
rectTransform = CGAffineTransformScale(rectTransform, UIScreen.mainScreen().scale, UIScreen.mainScreen().scale)
// apply the transformation to the rect to create a new, shifted rect
let transformedCropSquare = CGRectApplyAffineTransform(rect, rectTransform)
// use the rect to crop the image
let imageRef = CGImageCreateWithImageInRect(image.CGImage, transformedCropSquare)
// create a new UIImage and set the scale and orientation appropriately
let result = UIImage(CGImage: imageRef!, scale: image.scale, orientation: image.imageOrientation)
return result
}
Here are the functions to set and translate the mask view
func setTopMask(){
let path = CGPathCreateWithRect(cropViewMask.frame, nil)
topMaskLayer.path = path
topImageView.layer.mask = topMaskLayer
}
func translateMask(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(self.view)
sender.view!.center = CGPointMake(sender.view!.center.x + translation.x, sender.view!.center.y + translation.y)
// print(sender.translationInView(self.view))
sender.setTranslation(CGPointZero, inView: self.view)
// print("panned mask")
if sender.state == .Ended {
printFrames()
}
}
func setCropMaskFrame() {
let x = ulCorner.center.x
let y = ulCorner.center.y
let width = urCorner.center.x - ulCorner.center.x
let height = blCorner.center.y - ulCorner.center.y
cropViewMask.frame = CGRectMake(x, y, width, height)
setTopMask()
}
I know this was long time ago...Just a thought, I ran into similar problem and what I found is that the frames for cropping are most probably correct. The problem lies in the actual size of the picture you're trying to crop. I solved the issue by aligning sizes of my view which holds the picture, with the actual picture size (in points). Then the cropping area cropped what was selected. I know this is probably not a solution, just sharing my experience, hope it helps to turn on some lightbulbs :)

Why is my screenshot not displaying current view?

When I take a screenshot to share the current view of my device (iPhone), it only takes the upper part of it, and when I scroll down to the bottom (of my tableview at runtime), the screenshot is blank as if not capturing the current view on the device - I hope I am explaining alright there.
Am I missing anything?
func captureScreen() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, false, 0);
self.view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image:UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image
}
Change your code to the following :
func captureScreen() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.view.frame.size, false, 0);
self.view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image:UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image
}
Note the change from bound to frame in your code. The last argument in UIGraphicsBeginImageContextWithOptions has to do with the scale. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen
Learn more about it here