Creating PDF from UIView in Swift - swift

I have been stuck/ researching this problem for days, the method below successfully creates the pdf from a regular size UIView. However, I need the contents of my containerView that is located inside a UIScrollview. The height of my containerView is 5000. Can someone help me to render the containerView into multiple PDF pages or direct me to a better way of doing this. `func exportAsPdfFromView() -> String {
func screenShotScrollview() -> String {
let scrollView = waiverViews.scrollView
let pageDimensions = scrollView.bounds
let pageSize = pageDimensions.size
let totalSize = scrollView.contentSize
let numberOfPagesThatFitVertivally = Int(ceil(totalSize.height/pageSize.height))
let outputData = NSMutableData()
UIGraphicsBeginPDFContextToData(outputData, pageDimensions, nil)
let savedContentOffset = scrollView.contentOffset
let savedContentInset = scrollView.contentInset
scrollView.contentInset = UIEdgeInsets.zero
let origin = CGRect(x: 0, y: 0, width: pageSize.width, height: pageSize.height)
if let context = UIGraphicsGetCurrentContext() {
for indexVertical in 0..<numberOfPagesThatFitVertivally {
UIGraphicsBeginPDFPageWithInfo(origin, nil)
let offsetVertical = CGFloat(indexVertical) * pageSize.height
scrollView.contentOffset = CGPoint(x: 0, y: offsetVertical)
context.translateBy(x: 0, y: -offsetVertical)
scrollView.layer.render(in: context)
}
UIGraphicsEndPDFContext()
scrollView.contentInset = savedContentInset
scrollView.contentOffset = savedContentOffset
}
return self.saveViewPdf(data: outputData)
}

Essentially, a scrollView would not want to render all contents to its layer all the time, until it is being viewed.
I am guessing this is returning just the snapshot of the UIScrollView's current window?
To take multiple snapshots of the whole contentBounds of the UIScrollView, you could potentially, setTheContentOffset of the scrollview in its total contentBounds.height/frame.height which would return the pages. And then generate PDFs within the loop?
for instance something like this, and then UX wise, hide the scroll view. While this is being generated, unhide it when it is complete.
for i in 0..<pages {
scrollView.setContentOffset(.init(x: 0, y: i*scrollViewHeight))
//PDF Generation flow
//set the url to something like "pdf_file_\(i).pdf"
}

Related

Swift PDFKit draw multiple images in a single PDFPage

I'm trying to draw multiple images into a single PDFPage.
Looking at the docs and over StackOverflow seems like the best I got is to use PDFPage with an UIImage initializer like so:
let pdfPage = PDFPage(image: image)
But it just creates a page with a full-page image. I tried to draw the images using CGContext but I don't understand how to use PDFPage within a drawing context for it to draw the images rapidly like in the example below.
let bounds = page.bounds(for: .cropBox)
// Create a `UIGraphicsImageRenderer` to use for drawing an image.
let renderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
let image = renderer.image { (context) in
// How do I rapidly draw them here?
}
Any help will be highly appreciated!
The result I get with PDFPage(image: <UIImage>) vs expected result:
You probably need to improve the positioning logic for the image in the loop, but this should point you to the right direction.
import UIKit
import PDFKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let pdfView = PDFView()
pdfView.frame = CGRect(x: 0, y: 50, width: view.frame.width, height: view.frame.height - 50)
if let d = createPDFDocument() {
pdfView.document = d
}
view.addSubview(pdfView)
}
func createPDFDocument() -> PDFDocument? {
let pdfDocument = PDFDocument()
let page = PDFPage()
let bounds = page.bounds(for: .cropBox)
let imageRenderer = UIGraphicsImageRenderer(bounds: bounds, format: UIGraphicsImageRendererFormat.default())
let image = imageRenderer.image { (context) in
context.cgContext.saveGState()
context.cgContext.translateBy(x: 0, y: bounds.height)
context.cgContext.concatenate(CGAffineTransform.init(scaleX: 1, y: -1))
page.draw(with: .mediaBox, to: context.cgContext)
context.cgContext.restoreGState()
// Improve logic for image position
Range(1...4).forEach { value in
let image = UIImage(named: "YOUR_IMAGE_NAME")
let rect = CGRect(x: 50 * value, y: 0, width: 40, height: 100)
image?.draw(in: rect)
}
}
let newPage = PDFPage(image: image)!
pdfDocument.insert(newPage, at: 0)
return pdfDocument
}
}

Customising UItababbar swift

Can anyone point me how could I achieve such design in the UITabbar. I have tried adding the back-ground Image, but that does not look like the design. Here the curve is extended beyond the frame of UITabbar, not sure how to add this views on top of active tabbar.
Creating a custom TabBar from UITabBarController can be solved the problem. Instead of adding a direct image to the Tabbar, use an on the fly image using UIGraphicsBeginImageContext for selectedTabBackgroundImage.
Create the image.
Clip the top part round in the image
Here is the example of the code.
import UIKit
class CustomTabBarViewController: UITabBarController {
var topClipSize: CGFloat = 24.5 //Adjust based on the number of tabbar
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
let singleTabWidth: CGFloat = self.tabBar.frame.size.width / CGFloat((self.tabBar.items?.count)!)
let singleTabSize = CGSize(width:singleTabWidth , height: self.tabBar.frame.size.height)
// Create the backgound image
let selectedTabBackgroundImage: UIImage = self.imageWithColor(color: .blue, size: singleTabSize)
// Clip the top
self.tabBar.selectionIndicatorImage = selectedTabBackgroundImage.roundTopImage(topClipSize: topClipSize)
}
func imageWithColor(color: UIColor, size: CGSize) -> UIImage {
if size.height > 55 {
topClipSize = 30.0 // iPhone 8 tabbar height is 53 and iPnone X is 83 - We need more space on top.
}
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height + topClipSize)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context!.setFillColor(color.cgColor)
context!.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
extension UIImage {
func roundTopImage(topClipSize: CGFloat) -> UIImage {
let rect = CGRect(origin:CGPoint(x: 0, y: 0), size: self.size)
let rectBounds: CGRect = CGRect(x: rect.origin.x, y: rect.origin.y + (topClipSize * 2), width: rect.size.width, height: rect.size.height - (topClipSize * 2))
let ovalBounds: CGRect = CGRect(x: rect.origin.x - topClipSize, y: rect.origin.y, width: rect.size.width + (topClipSize * 2), height: rect.size.height)
UIGraphicsBeginImageContextWithOptions(self.size, false, 1)
let rectPath = UIBezierPath(rect: rectBounds)
let ovalPath = UIBezierPath(ovalIn: ovalBounds)
rectPath.append(ovalPath)
rectPath.addClip()
self.draw(in: rect)
return UIGraphicsGetImageFromCurrentImageContext()!
}
}
Here is the output:
It's not really possible to elegantly change the native UITabBar's appearance to that extent. Your options are to create a custom container view controller that acts like a UITabBarController, or just hide the default tab bar and implement your own view in that space.
Even though it's less elegant because you'd be just throwing a view on top of the default tab bar, I actually like that method because you retain the benefits of the native UITabBarController (calling self.tabBarController? from its view controllers, it already adjusts layout margins, etc).
To do this, in your subclass of UITabBarController hide the tabBar:
self.tabBar.isHidden = true
self.tabBar.alpha = 0
Then after implementing your custom view however you want, just set the frame of your custom view to self.tabBar.frame in viewDidLayoutSubviews.
For changing viewControllers, call this when the user taps one of your custom tabs:
self.selectedIndex = newIndex

how to save a cropped uiview screen shot using a extension market

My extension method right now takes a screenshot of the entire uiview inside of the view controller. I would like to use the same function to do the same thing only take a exact area of of the uiview instead of the whole view. Specifically I would like to capture x:0,y:0,length 200,Height 200,
func screenshot() -> UIImage {
let imageSize = UIScreen.main.bounds.size as CGSize;
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
let context = UIGraphicsGetCurrentContext()
for obj : AnyObject in UIApplication.shared.windows {
if let window = obj as? UIWindow {
if window.responds(to: #selector(getter: UIWindow.screen)) || window.screen == UIScreen.main {
// so we must first apply the layer's geometry to the graphics context
context!.saveGState();
// Center the context around the window's anchor point
context!.translateBy(x: window.center.x, y: window.center
.y);
// Apply the window's transform about the anchor point
context!.concatenate(window.transform);
// Offset by the portion of the bounds left of and above the anchor point
context!.translateBy(x: -window.bounds.size.width * window.layer.anchorPoint.x,
y: -window.bounds.size.height * window.layer.anchorPoint.y);
// Render the layer hierarchy to the current context
window.layer.render(in: context!)
// Restore the context
context!.restoreGState();
}
}
}
let image = UIGraphicsGetImageFromCurrentImageContext();
return image!
}
How about:
extension UIView {
func screenshot(for rect: CGRect) -> UIImage {
return UIGraphicsImageRenderer(bounds: rect).image { _ in
drawHierarchy(in: CGRect(origin: .zero, size: bounds.size), afterScreenUpdates: true)
}
}
}
This makes it a bit more reusable, but you can change it to be a hardcoded value if you want.
let image = self.view.screenshot(for: CGRect(x: 0, y: 0, width: 200, height: 200))

Swift ImageCropper returns an image outside of the specified "window"

I am making an app that has a UIWebView along with a button on a single view controller. When the button is clicked, an image (of the UIWebView) is captured using UIGraphicsContext.
This part works great! But when the button is clicked, after capturing the image, it displays the image as a subview on the same view, and I have been trying to use an ImageCropper Library that draws a CGRect in another subview over the UIImageView on the screen with a submit button. The rectangle itself can be resized (dragging the corners/edges) and moved around the view.
When the submit button is clicked, another subview is displayed in the top left hand portion of screen and display the image that was cropped (after clicking submit button) The idea is to only capture what is inside the rectangle. I am able to get the code working but the image captured is of the same image but not a section that is inside the CGRect.
I have 3 images that show how it works and shows the image that is cropped incorrectly.enter image description here . Shot 1 . Shot 2
Shot 3. I believe my problems lies within the size of image captured and the size of the image with the crop rect are not equal and that is why it is distorting it.
Does anyone know what might be the cause? Sorry for the long winded question but any help would be greatly appreciated!
Here is my code below:
ViewController.swift:
class ViewController: UIViewController {
#IBOutlet var webView: UIWebView!
#IBOutlet var imageView: UIImageView!
override func viewDidLoad() {
imageView.isHidden = true
let aString = URL(string: "https://www.kshuntfishcamp.com/home.page")
webView.loadRequest(URLRequest(url: aString!))
super.viewDidLoad()
}
#IBAction func takePhotoPressed(_ sender: UIButton) {
UIGraphicsBeginImageContextWithOptions(webView.bounds.size, false, 0.0)
if let aContext = UIGraphicsGetCurrentContext(){
webView.layer.render(in: aContext)
}
let capturedImage:UIImage? = UIGraphicsGetImageFromCurrentImageContext()
imageView = UIImageView(frame: CGRect(x: 22, y: 123, width: 330, height: 330))
let image = capturedImage
imageView.image = image
imageView.contentMode = UIViewContentMode.scaleAspectFill
imageView.clipsToBounds = true
imageView.isHidden = true
webView.isHidden = true
let editView = EditImageView(frame: self.view.frame)
let image2 = capturedImage!
editView.initWithImage(image: image2)
let croppedImage = editView.getCroppedImage()
self.view.addSubview(editView)
self.view.backgroundColor = UIColor.clear
UIImageWriteToSavedPhotosAlbum(croppedImage, nil, nil, nil)
}
EditImageView.swift - source (https://github.com/Thanatos-L/LyEditImageView)-only including parts that seem relevant to solving the problem
func initWithImage(image:UIImage){
imageView = UIImageView(frame: CGRect(x: 22, y: 123, width: 330, height: 330))
imageView.tag = IMAGE_VIEW_TAG;
self.addSubview(self.imageView)
imageView.isUserInteractionEnabled = true;
imageView.image = image
imageView.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
let frame = AVMakeRect(aspectRatio: imageView.frame.size, insideRect: self.frame);
imageView.frame = frame
originImageViewFrame = frame
NSLog("initWithImage %#", NSStringFromCGRect(originImageViewFrame))
imageZoomScale = 1.0
commitInit()
}
private func cropImage() {
let rect = self.convert(cropView.frame, to: imageView)
let imageSize = imageView.image?.size
let ratio = originImageViewFrame.size.width / (imageSize?.width)!
let zoomedRect = CGRect(x: rect.origin.x / ratio, y: rect.origin.y / ratio, width: rect.size.width / ratio, height: rect.size.height / ratio)
let croppedImage = cropImage(image: imageView.image!, toRect: zoomedRect)
var view: UIImageView? = self.viewWithTag(1301) as? UIImageView
if view == nil {
view = UIImageView()
}
view?.frame = CGRect(x: 0, y: 0, width: croppedImage.size.width , height: croppedImage.size.height)
view?.image = croppedImage
view?.tag = 1301
self.addSubview(view!)
}

Facebook Picture zoom animation in swift, picture not showing the initial position

I've been trying to make a zoom animation like Facebook when you click a picture into a cell to move into the middle of the screen. The animation works, but for a reason that I can not figure out, it is not starting from the initial position it is giving me another frame. Please help, I've been struggling with this for a few days now.
I am using a collectionView with CustomCell and everything it's done programmatically:
The function in CenterVC:
//MARK: Function to animate Image View (it will animate to the middle of the View)
func animateImageView(statusImageView : UIImageView) {
//Get access to a starting frame
statusImageView.frame.origin.x = 0
if let startingFrame = statusImageView.superview?.convert(statusImageView.frame, to: nil) {
//Add the view from cell to the main view
let zoomImageView = UIView()
zoomImageView.backgroundColor = .red
zoomImageView.frame = statusImageView.frame
view.addSubview(zoomImageView)
print("Starting frame is: \(startingFrame)")
print("Image view frame is: \(statusImageView.frame)")
UIView.animate(withDuration: 0.75, animations: {
let height = (self.view.frame.width / startingFrame.width) * startingFrame.height
let y = self.view.frame.height / 2 - (height / 2)
zoomImageView.frame = CGRect(x: 0, y: y, width: self.view.frame.width, height: height)
})
}
}
This is the pictureView inside the cell and the constraints (this is where I am setting up the picture for the view, and I am using in cellForRowAtIndexPath cell.centerVC = self):
var centerVC : CenterVC?
func animate() {
centerVC?.animateImageView(statusImageView: pictureView)
}
let pictureView : UIImageView = {
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.image = UIImage(named: "cat")
imageView.backgroundColor = UIColor.clear
imageView.layer.masksToBounds = true
imageView.isUserInteractionEnabled = true
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
addConstraintsWithFormat(format: "V:|-5-[v0(40)]-5-[v1]-5-[v2(200)]", views: profileImage, postTextView, pictureView)
addConstraintsWithFormat(format: "H:|[v0]|", views: pictureView)
This is what it prints out in the debugger:
Starting frame is: (5.0, 547.5, 365.0, 200.0)
Image view frame is: (0.0, 195.5, 365.0, 200.0)
As you can see the starting frame it's different from the initial frame and position of the picture. The animation it's not leaving the initial position it just appears somewhere on top and animates to the middle. I don't know what to do, please advice.
For some reason, it was not reading the starting frame rect so I've made a new CGRect that gets the origin and set the size of the picture: (it animates perfectly now)
zoomImageView.frame = CGRect(origin: startingFrame.origin, size: CGSize(width: view.frame.width, height: statusImageView.frame.height))