How to set the PDFPage Label and redraw the thumbnail? - swift

I'm trying to create a multipage PDF file from NSImages in swift on macOS. How do i set the PDFPage label and how do i trigger the generation of the preview thumbnails.
PDFPage has a variable for the label:
https://developer.apple.com/documentation/pdfkit/pdfpage/1504088-label
but i didn't find a way to set the label i tried to set the label to 1 with: page.setValue("1", forKey: "label") but this did nothing
I didn't find a way to trigger the generation of thumbnails.
code example for one page:
//stroyboard pdfView
#IBOutlet weak var myPdfView: PDFView!
//Storyboad PDFThumbnailView
#IBOutlet weak var myPDFThumbnailView:PDFThumbnailView!
//create a new PDFDocument
var myPDFDocument = PDFDocument()
//image path
guard let pngPath = Bundle.main.url(forResource: "example", withExtension: "png") else { return }
//create NSImage
if let firstPageImage = NSImage(contentsOf: pngPath){
//PDFPage
if let page = PDFPage(image: firstPageImage) {
//insert Page in PDFDocument
myPDFDocument.insert(page, at: myPDFDocument.pageCount)
}
}
//set PDFDocument
myPdfView.document = myPDFDocument
//set PDFView
myPDFThumbnailView.pdfView = myPdfView
Expected result:
a thumbnail of the PDF page with the label "1" appears in PDFThumbnailView and the PDF pages are rendered in PDFView.
Actual result:
a blank thumbnail with the label "Label" appears in PDFThumbnailView
and the PDF pages are rendered in PDFView.
EDIT:
My workaround to solve my problems is to save the PDFDocument to disk and reload it. It's not pretty but it's working... any advice is welcome!
myPDFDocument.write(toFile: "path/to/temp/folder/mytemp.pdf")
if let document = PDFDocument(url: URL(fileURLWithPath: "path/to/temp/folder/mytemp.pdf"))
{
myPDFDocument = document
}

There doesn't appear to be a method for setting the label of a PDFPage. The label property is Read-only.
This value is actually the displayed page number, so shouldn't normally need setting.

Related

Not getting selection from PDFView

I see somewhere this code for highlight selected text in pdf document:
let select = pdfView.currentSelection?.selectionsByLine()
//assuming for single-page pdf.
guard let page = select?.first?.pages.first else { return }
select?.forEach({ selection in
let highlight = PDFAnnotation(bounds: select.bounds(for: page), forType: .highlight, withProperties: nil)
highlight.endLineStyle = .square
highlight.color = UIColor.orange.withAlphaComponent(0.5)
page.addAnnotation(highlight)
})
for me ,
let select = pdfView.currentSelection?.selectionsByLine()
is always giving nil. where to put this code. I putting it in Gesture Recogniser Delegate methods.
Also it seems adding multiple annotation on page. Do we get single highlight annotation per selection on a page?

UIImage(named: String) or UIImage swift 4

What way is more convenient to set image from my app assets in my app Image View? I have two ways: the first one is function UIImage(named: String) or UIImage and both is working for me, but I want to know which one is the best ,so I can use one in the future
here is two examples
// first
let myImages1 = ["dice1", "dice2", "dice3", "dice4", "dice5", "dice6"]
#IBOutlet weak var diceImageView1: UIImageView!
diceImageView1.image = UIImage(named: myImages1[index1])
// second
let myImages2 = [ image1, image2, image3, image4, image5, image6 ]
#IBOutlet weak var diceImageView2: UIImageView!
diceImageView2.image = myImages2[index2]
We're using Images from assets catalog instead String names. It's the better way to set UIImage. But from swift 4.2 we can't use asset names anymore.
We should use image literal.
Here's the example:
let logoImageView: UIImageView = {
let iv = UIImageView()
// Set image with image literal
iv.image = #imageLiteral(resourceName: "feedback")
return iv
}()
Look at the screenshot below.
After you've typed Image Literal you can double click on that and choose your image from assets catalog. Here is the screenshot.
But last time I use this lib R.swift. Get strong typed, autocompleted resources like images, fonts and segues in Swift projects.
How it looks in code:
You can create an extension for UIImage and have static variables holding your images.
extension UIImage {
static var dice1: UIImage? {
return UIImage(named: "dice1")
}
}
let imageView = UIImageView()
imageView.image = UIImage.dice1
Or you can use image literals in Xcode which are nice because you can see the actual image

NSimageView.image won't display an image in swift

I have an issue with a OSX application. I want it to display an image, so I create an NSImageView object, and linked it to my view controller. I then ceate a function that should load an image from a selected folder. I get the image url correctly an dtry to load the image onto the NSImageView but that doesn't work (with no rpeorted error). The image is a tiff file, and the isValid variable return false it seems...
imageAve is the name of the NSImageView outlet :
#IBOutlet weak var imageAve: NSImageView!
Here the code of the function in question :
func loadImage() { //load the tiff file
let image_url = String(describing: self.loadedtraces.tracefilesurls[self.indexSelected])
let image_url_cut = String(image_url[image_url.index(image_url.startIndex, offsetBy: 7)...image_url.index(image_url.endIndex, offsetBy: -8)] + "_ave_merged.tif")
if FileManager.default.fileExists(atPath: image_url_cut) { //test if the file exists and that works fine.
let image_average = NSImage(byReferencingFile: image_url_cut)
if (image_average?.isValid == false) {
self.alert.informativeText = "The image is invalid."
self.alert.alertStyle = .informational
self.alert.icon = alert_info_logo
self.alert.beginSheetModal(for: self.view.window!)
} else {
self.imageAve.needsDisplay = true
self.imageAve.image = image_average
}
} else {
self.alert.informativeText = "The average merge image file does not exist in the traces folder."
self.alert.alertStyle = .informational
self.alert.icon = alert_info_logo
self.alert.beginSheetModal(for: self.view.window!)
}
}
Thanks to everyone who might able to help !

Swift Mac OS - How to play another video/change the URL of the video with AVPlayer upon a button click?

I am new to swift and I am trying to make a Mac OS app that loops a video from the app's resources using AVPlayer as the background of the window once the app has been launched. When the user selects a menu item/clicks a button the background video will instantly change to a different video from the app's resources and start looping that video as the window's background.
I was able to play the first video once the app launches following this tutorial: (https://youtu.be/QgeQc587w70) and I also successfully made the video loop itself seamlessly following this post: (Looping AVPlayer seamlessly).
The problem I am now facing is changing the video to the other one once a menu item was selected/a button was clicked. The approach I was going for is to change the url and create a new AVPlayer using the new URL and affect it to the playerView.player following this post: (Swift How to update url of video in AVPlayer when clicking on a button?). However every time the menu item is selected the app crashes with the error "thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)". This is apparently caused by the value of playerView being nil. I don't really understand the reason for this as playerView is an AVPlayerView object that I created using the xib file and linked to the swift file by control-dragging and I couldn't seem to find another appropriate method of doing the thing I wanted to do. If you know the reason for this and the way of fixing it please provide me some help or if you know a better method of doing what I've mention above please tell me as well. Any help would be much appreciated!
My code is as follow, the line that crashes the app is at the bottom:
import Cocoa
import AppKit
import AVKit
import AVFoundation
struct videoVariables {
static var videoName = "Test_Video" //declaring the video name as a global variable
}
var videoIsPlaying = true
var theURL = Bundle.main.url(forResource:videoVariables.videoName, withExtension: "mp4") //creating the video url
var player = AVPlayer.init(url: theURL!)
class BackgroundWindow: NSWindowController {
#IBOutlet weak var playerView: AVPlayerView! // AVPlayerView Linked using control-drag from xib file
#IBOutlet var mainWindow: NSWindow!
#IBOutlet weak var TempBG: NSImageView!
override var windowNibName : String! {
return "BackgroundWindow"
}
//function used for resizing the temporary background image and the playerView to the window’s size
func resizeBG() {
var scrn: NSScreen = NSScreen.main()!
var rect: NSRect = scrn.frame
var height = rect.size.height
var width = rect.size.width
TempBG.setFrameSize(NSSize(width: Int(width), height: Int(height)))
TempBG.frame.origin = CGPoint(x: 0, y: 0)
playerView!.setFrameSize(NSSize(width: Int(width), height: Int(height)))
playerView!.frame.origin = CGPoint(x: 0, y: 0)
}
override func windowDidLoad() {
super.windowDidLoad()
self.window?.titleVisibility = NSWindowTitleVisibility.hidden //hide window’s title
self.window?.styleMask = NSBorderlessWindowMask //hide window’s border
self.window?.hasShadow = false //hide window’s shadow
self.window?.level = Int(CGWindowLevelForKey(CGWindowLevelKey.desktopWindow)) //set window’s layer as desktopWindow layer
self.window?.center()
self.window?.makeKeyAndOrderFront(nil)
NSApp.activate(ignoringOtherApps: true)
if let screen = NSScreen.main() {
self.window?.setFrame(screen.visibleFrame, display: true, animate: false) //resizing the window to cover the whole screen
}
resizeBG() //resizing the temporary background image and the playerView to the window’s size
startVideo() //start playing and loop the first video as the window’s background
}
//function used for starting the video again once it has been played fully
func playerItemDidReachEnd(notification: NSNotification) {
playerView.player?.seek(to: kCMTimeZero)
playerView.player?.play()
}
//function used for starting and looping the video
func startVideo() {
//set the seeking time to be 2ms ahead to prevent a black screen every time the video loops
let playAhead = CMTimeMake(2, 100);
//loops the video
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object:
playerView.player?.currentItem, queue: nil, using: { (_) in
DispatchQueue.main.async {
self.playerView.player?.seek(to: playAhead)
self.playerView.player?.play()
}
})
var playerLayer: AVPlayerLayer?
playerLayer = AVPlayerLayer(player: player)
playerView?.player = player
print(playerView?.player)
playerLayer?.videoGravity = AVLayerVideoGravityResizeAspectFill
player.play()
}
//changing the url to the new url and create a new AVPlayer then affect it to the playerView.player once the menu item is being selected
#IBAction func renderBG(_ sender: NSMenuItem) {
videoVariables.videoName = "Test_Video_2"
var theNewURL = Bundle.main.url(forResource:videoVariables.videoName, withExtension: "mp4")
player = AVPlayer.init(url: theNewURL!)
//!!this line crashes the app with the error "thread 1 exc_bad_instruction (code=exc_i386_invop subcode=0x0)" every time the menu item is being selected!!
playerView.player = player
}
}
Additionally, the background video is not supposed to be interactive(E.g. User cannot pause/ fast-forward the video), so any issues that might be caused by user interactivity can be ignored. The purpose of the app is to play a video on the user's desktop creating the exact same effect of running the command:
"/System/Library/Frameworks/ScreenSaver.framework/Resources/
ScreenSaverEngine.app/Contents/MacOS/ScreenSaverEngine -background" in terminal.
Any help would be much appreciated!
You don't need to create AVPlayer from url. There is AVPlayerItem class to manipulate player playback queue.
let firstAsset = AVURLAsset(url: firstVideoUrl)
let firstPlayerItem = AVPlayerItem(asset: firstAsset)
let player = AVPlayer(playerItem: firstPlayerItem)
let secondAsset = AVURLAsset(url: secondVideoUrl)
let secondPlayerItem = AVPlayerItem(asset: secondAsset)
player.replaceCurrentItem(with: secondPlayerItem)
Docs about AVPlayerItem

How do I draw something on a PDF in Swift?

Here is what I am doing currently.
First, I get the document. Next I create a PDF View and assign the document to the view. Then I create a view and do my drawing in the view, then add the view to the PDFView as a subview. Then I convert the PDFView to data, then to PDF document. Since I'm doing the drawing on the first page of the PDF, I remove the first page from the original PDF, insert the new first page, and return the document.
guard let document = PDFDocument(url: url) else { return nil }
guard let page = document.page(at: 0) else { return nil }
let pageBounds = page.bounds(for: .mediaBox)
let pdfView = PDFView(frame: pageBounds)
pdfView.document = document
pdfView.enclosingScrollView?.autohidesScrollers = true
let view = /// code for subview that I am drawing on
pdfView.addSubview(view)
let data = pdfView.dataWithPDF(inside: pageBounds)
guard let pdf = PDFDocument(data: data) else { return nil }
document.removePage(at: 0)
document.insert(pdf.page(at: 0)!, at: 0)
return document
Is there a better way to do this? To add a wrinkle, my final product has a weird scroll bar image (see Screenshot). I tried adding auto hide scrollers & enclosingScrollView?.hasVerticalScroller = false but neither seem to hide the scroll bar.
Thanks in advance!
So I've solved my own problem. For anyone else stuck like me, here is what I did. For example to draw a box on a page:
create a CGContext (ctx) for PDFDocuments. You can do this either with data or with a URL you want to write to.
create a CGPDFDocument with the document you want to edit
get the CGPage of the CGPDF you want to edit (cgPage)
and:
ctx?.beginPDFPage(nil)
ctx?.drawPDFPage(cgPage)
ctx?.beginPath()
let path = CGPath(roundedRect: box as CGRect, cornerWidth: 5, cornerHeight: 5, transform: nil)
ctx?.setStrokeColor(CGColor.black)
ctx?.setFillColor(color.cgColor)
ctx?.setLineWidth(10.0)
ctx?.addPath(path)
ctx?.strokePath()
ctx?.addPath(path)
ctx?.fillPath()
ctx?.endPDFPage()
ctx?.closePDF()
(If you created the context with a URL, close will write to disk. Otherwise you'll have to do something with the PDF data.)
A simple solution is demonstrated in the 2017 WWDC video on PDFKit. I assume you have an existing document to which you want to add some drawing. Here's what to do:
Give the document a delegate.
In the delegate, implement classForPage to declare a class for your pages: that class should be your own PDFPage subclass.
In your PDFPage subclass, implement draw(with:to:). You are given a context; draw into it! Be sure to call super if you want the default drawing to happen.
If your PDFPage subclass needs to draw different things on different pages, it can find out where it is in the document by asking for self.document?.index(for:self).