Swift PDFKit: Unable to scale view to a small size - ios

I am attempting to display a pdf document on a pdfView and would like it to scale such that I can see the full content view within the bounds of the device. However, I am unable to scale it small enough to do so. I have attempted some solutions on SO but still doesn't work.
My implementation so far:
let pdfView: PDFView = {
let v = PDFView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
//addSubview and autolayout constraints to superview
if let url = Bundle.main.url(forResource: "documentName", withExtension: "pdf"), let document = PDFDocument(url: url) {
pdfView.document = document
guard let page = document.page(at: 0) else {return}
let pageSize = page.bounds(for: .cropBox)
let screenSize = UIScreen.main.bounds
let scale = screenSize.width / pageSize.width
pdfView.scaleFactor = scale
}
}
Note that these are things I have tried but doesn't work:
pdfView.autoScales = true
pdfView.displayMode = .singlePage
pdfView.minScaleFactor = // to a very small value
I also attempted to iterate the scaleFactor manually by setting 0.01 but realised that the view will not scale lower than 0.25. Is there a way for me to scale even lower? The order of scaling in my appliction is in the order of 0.1-0.2. Thanks.

If I remember correctly, PDFKit seems a bit buggy concerning autoScales. PDFView must be set up in the correct order. You can try:
Add PDFView in subview in viewDidLoad
Set up other PDFView properties, like displayMode, displayDirection, and set up constraint for PDFView in viewWillAppear
Finally, set pdfView.document = document then pdfView.autoScales = true
This setup order work for me.
I don't have answer for your next question. But it could also be related to the setup order. Let me know if it works for you.
Hope this helps.

Related

PDFPage thumbnail (UIImage) does not respect PDFPage orientation in iOS 13 (Xcode 13.1)

Description
I am having issue with UIImage "generated" from PDFPage via method .thumbnail(of:for). UIImage, which I later display in UIImageView, does not respect rotation of the PDF page, in iOS 13 only.
When PDF page is rotated for example by 90° and presented in PDFView all is good and shown correctly. But when I want to take the UIImage (i.e. thumbnail) of the PDFPage and show it in UIImageView, for some reason, iOS 13 does not respect the initial rotation of the PDF page and present it in UIImageView incorrectly.
Please behold on below sample code and example screenshots.
Testing environment
iOS 13 on simulator only, not having real device
Xcode 13.1
Sample code
for sake of shorter example, I force unwrap where optionals occur
import UIKit
import PDFKit
class TestingViewController: UIViewController {
lazy var imageView: UIImageView = {
let iv = UIImageView()
iv.translatesAutoresizingMaskIntoConstraints = false
return iv
}()
// Pass URL of the PDF document when instantiating this VC
init(withURL: URL) {
super.init(nibName: nil, bundle: nil)
let pdfDoc = PDFDocument(url: withURL)
let pdfPage = pdfDoc!.page(at: 0) // Getting first page of the doc
let pdfPageRect = pdfPage!.bounds(for: .mediaBox)
imageView.image = pdfPage!.thumbnail(of: pdfPageRect.size, for: .mediaBox) // Getting the UIImage of the PDF page and assign it to the needed image view
imageView.contentMode = .scaleAspectFit
imageView.clipsToBounds = true
print("\(pdfPage?.rotation)") // Prints the correct rotation value, even though UIImageView does not respect it
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// Layout only
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemPink
view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
imageView.widthAnchor.constraint(equalToConstant: 350),
imageView.heightAnchor.constraint(equalToConstant: 700),
])
}
}
Example screenshots from simulator
✅ iOS 15, shown correctly
❌ iOS 13.2, shown incorrectly

Swift: Programmatically add multiple stack view one below the other through a loop

I have a view controller created in the storyboard and the it contains (check image for reference)
ScrollerView
a. StackViewA (image: green)
i. LabelA
ii. LabelB
b. StackViewB (image: green)
i. LabelC
ii. LabelD
I am fetching data from the API and am able to show that data in those labels.
Now, the 3rd set of data that I am fetching is dynamic, meaning it can be 2 more StackView (image: red) under the second StackView or 3 more etc.
I am guessing that I have the add that StackView programmatically in the controller inside the loop so that is created according to the loop.
Currently my 3rd StackView is also created in the storyboard and therefore it is showing only the last data from 3rd set after looping through them.
How do I solve that?
More specifically:
How can I add a StackView inside the ScrollerView created in the storyboard.
How do I contains it to position itself below the 2nd StackView also created in the storyboard.
Update
class InfoDetailsViewController: UIViewController {
// MARK: - Outlets
#IBOutlet weak var infoStack: UIStackView!
#IBOutlet weak var mainScroll: UIScrollView!
static var apiResp: APIDetailsResponse.APIReturn?
let infos: [APIDetailsResponse.Info] = (APIDetailsViewController.apiResp?.content?.infos)!
override func viewDidLoad() {
super.viewDidLoad()
infoStack.spacing = 25.0
for info in infos {
let addInfoTitle = UILabel()
addInfoTitle.font = .preferredFont(forTextStyle: .body)
addInfoTitle.backgroundColor = .orange
addInfoTitle.translatesAutoresizingMaskIntoConstraints = false
let addInfoContent = UITextView()
addInfoContent.font = .preferredFont(forTextStyle: .body)
addInfoContent.backgroundColor = .green
addInfoContent.translatesAutoresizingMaskIntoConstraints = false
addInfoTitle.text = "\(String(describing: (info.info_title)!))"
let htmlString = "\(String(describing: (info.information)!))"
// works even without <html><body> </body></html> tags, BTW
let data = htmlString.data(using: String.Encoding.unicode)!
let attrStr = try? NSAttributedString(
data: data,
options: [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil)
addInfoContent.attributedText = attrStr
let childStackView = UIStackView(arrangedSubviews: [addInfoTitle, addInfoContent])
childStackView.alignment = .fill
childStackView.axis = .vertical
childStackView.distribution = .fill
childStackView.spacing = 5.0
childStackView.translatesAutoresizingMaskIntoConstraints = false
infoStack.addArrangedSubview(childStackView)
}
}
Currently I have this. What is happening now is no matter how many data the array is returning, I am always getting title and content for the first one and only title for each consecutive data.
So the best solution is when you create your scrollview add a stack view inside it. And whenever you are creating stack view dynamically add under that stack view which is inside scrollview.
So in this case you new stack view will gets stacked under your outer stack view in proper manner.
ScrollView -> 1 Stackview -> Multiple Dynamic stack views in loop
lets say you already have stack view named ParentStackview from your storyboard. Then follow these steps
lazy var childStackView: UIStackView = {
let stackView = UIStackView()
stackView.alignment = .center
stackView.axis = .vertical
stackView.distribution = .equalCentering
stackView.spacing = 30.0
return stackView
}()
public func viewDidLoad() {
ParentStackview.alignment = .center
ParentStackview.axis = .vertical
ParentStackview.distribution = .equalCentering
ParentStackview.spacing = 10.0
for eachChild in data {
ParentStackView.addArrangedSubview(childStackView)
childStackView.translatesAutoresizingMaskIntoConstraints = false
childStackView.widthAnchor.constraint(equalTo: securityScrollView.widthAnchor)
//set height too
// this below function you can use to add eachChild data to you child stackview
configureStackview(childstackView, eachChild)
}
}
Enjoy!

UICollectionView inside UIViewController not allowing scroll

Edit1: I am not using a storyboard everything has been added programmatically
Edit2: Updated code snippet
Hello, i've got the following issues with UICollectionView
The CollectionView won't allow you to scroll no matter what you do.
Sometimes the cells even disappear complete leaving you with a white background CollectionView.
The problem occurs when you try to scroll up to view the cells that are not visible.
I've create a CollectionView which has different type of cells.
The CollectionView is nested inside a ViewController below an ImageView.
Constraints are added and they work just fine.
How do i make it scrollable?
GIF representing the problem
ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Featured"
self.view.backgroundColor = UIColor.white
// Add UImage carousel
let carousel: UIImageView = {
let imageView = UIImageView()
let image = UIImage()
imageView.backgroundColor = UIColor.purple
return imageView
}()
self.view.addSubview(carousel)
carousel.translatesAutoresizingMaskIntoConstraints = false
// Add CollectionView
let featuredControllerLayout = UICollectionViewFlowLayout()
// Add CollectionViewController
featuredControllerLayout.scrollDirection = .vertical
let featuredController = FeaturedCollectionViewController(collectionViewLayout: featuredControllerLayout)
guard let featuredView = featuredController.collectionView else { return }
self.view.addSubview(featuredView)
featuredView.translatesAutoresizingMaskIntoConstraints = false
// Setup Constraints
if #available(iOS 11.0, *) {
let guide = self.view.safeAreaLayoutGuide
let guideSize = guide.layoutFrame.size
carousel.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
carousel.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
carousel.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
carousel.frame.size.height = guideSize.width/2
carousel.heightAnchor.constraint(equalToConstant: carousel.frame.size.height).isActive = true
featuredView.trailingAnchor.constraint(equalTo: guide.trailingAnchor).isActive = true
featuredView.leadingAnchor.constraint(equalTo: guide.leadingAnchor).isActive = true
featuredView.topAnchor.constraint(equalTo: carousel.bottomAnchor).isActive = true
featuredView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
}
}
Heyy what you are telling is you are not able to scroll up and see the entire collection view items ?
if that is the case then you have not accounted the height of tabbar controller so you can enable translucent for example I have a git repo I made you can check and let me know if you are stuck anywhere
https://github.com/dwivediashish00/socialApp

AVPlayerLayer not sizing correctly

I have a custom UITableViewCell that displays either an image or a video. If there's a video, it initially downloads and displays the thumbnail from that video while the video is loading and preparing to play.
It all works, the problem is that I get the ratio of the thumbnail, store it in the database and when I download the thumbnail, it makes the height based on that ratio. It works great for images. But the issue is that the AVPlayerLayer frame doesn't cover the entire thumbnail and it looks like this:
Here's how I do it:
func updateView() {
if let videoUrlString = post?.videoUrl, let videoUrl = URL(string: videoUrlString) {
volumeView.isHidden = false
player = AVPlayer(url: videoUrl)
playerLayer = AVPlayerLayer(player: player)
playerLayer!.frame = postImageView.frame // thumbnail image
playerLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill
contentView.layer.addSublayer(playerLayer!)
self.volumeView.layer.zPosition = 1
layoutIfNeeded()
player?.play()
player?.isMuted = videoIsMuted
}
if let ratio = post?.ratio {
photoHeightConstraint.constant = UIScreen.main.bounds.width / ratio
layoutIfNeeded()
}
}
I'm not doing something right apparently or I'm missing something based on my final result. Any clue?
UPDATE:
I notice that if you scroll the cells slowly, the video is being displayed according to the size of the thumbnail. But if you scroll fast, it mismatches the frame. I assume it has something to do with reusability or it simply cannot catch up with the frame? Here's my code for prepareForReuse() method:
override func prepareForReuse() {
super.prepareForReuse()
profileImageView.image = UIImage(named: "placeholder")
postImageView.image = UIImage(named: "profile_placeholder")
volumeView.isHidden = true
if let p = player, let pLayer = playerLayer {
p.pause()
pLayer.removeFromSuperlayer()
}
}
The solution turned out to be rather simple. Basically each post contains either post or a video, but either way, it always contains an image. I also set the ratio of that image (the width devided by the height). So, all I had to do is set the player's width to the screen width devided by the ratio and it gives your the proper frame.
playerLayer.frame.size.height = UIScreen.main.bounds.width / postRatio

PDFKit Swift control zoom

I want to control the zoom level of pdf file. By default when it launches it doesn't fits the screen. Even manually (pinch zoom) zoom-in and zoom-out doesn't make it fit the page. I have set the autoScales for true.
Also tried playing with below variable but it didn't help
open var scaleFactorForSizeToFit: CGFloat { get }
Below is my code for the same
pdfView = PDFView(frame: view.bounds)
pdfdocument = PDFDocument(url: filePathUrl!)
pdfView.document = pdfdocument
pdfView.displayMode = PDFDisplayMode.singlePageContinuous
pdfView.autoScales = true
view.addSubview(pdfView)
Some control were user can control zoom level.
Can it be achieved using gesture recognizer?
Any Hint in right direction would be highly appreciated
You can use scaleFactor to adjust zooming using UISlider.
pdfView.scaleFactor = 0.5

Resources