How to detect scrolling to another page in PDF using PDFkit - swift

I'm trying to add current page number
so when the user scrolling to another page it will display
for example
2 of 3 (if the pdf have 3 pages)
for now, I use this code
it will display always 1 of 3
I think PDFkit using UIScrollView
so I need to reach to the ScrollView that way I could detect when scrolling to another page and then increase the current page number
I didn't find any way to reach to scrollview in PDFkit
func showCurrentPageIndex(){
guard let totalPages = pdfContainerView.document?.pageCount else {return}
guard let currentPage = pdfContainerView.currentPage else {return}
guard let currentPageRef = currentPage.pageRef else {return}
let pageIndex = currentPageRef.pageNumber
totalPageNumbers.text = "\(totalPages)"
currentPageIndex.text = "\(pageIndex)"
}

Use a notification (PDFViewPageChanged).
import UIKit
import PDFKit
class ViewController: UIViewController, PDFViewDelegate {
// MARK: - Variables
var totalCount = Int()
// MARK: - IBOutlet
#IBOutlet weak var pdfView: PDFView!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let filePath = "Some file document path"
pdfView.document = getDocument(path: filePath)
if let total = pdfView.document?.pageCount {
totalCount = total
}
pdfView.backgroundColor = .lightGray
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
pdfView.usePageViewController(true, withViewOptions: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
/* notification */
NotificationCenter.default.addObserver (self, selector: #selector(handlePageChange), name: Notification.Name.PDFViewPageChanged, object: nil)
}
func getDocument(path: String) -> PDFDocument? {
let pdfURL = URL(fileURLWithPath: filePath)
let document = PDFDocument(url: pdfURL)
return document
}
}

Related

How to programmatically save the content written in TextView to a text file when terminating the app?

I want to save the content of the text view when the user closes the app.
I used the following codes to do so, but I cannot get the up-to-date string of the textview when closing the app. So, the produced text file is blank.
How should I access to the NSTextView from AppDelegate to save its content?
ViewController.swift
import Cocoa
class ViewController: NSViewController {
static var textViewString: String = ""
#IBOutlet var textView: NSTextView!{
didSet{
ViewController.textViewString = textView.string
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// start with hidden and show after moving to the main screen
DispatchQueue.main.async {
//keep the window top
self.view.window?.level = .floating
//set up the main display as the display where window shows up
let screens = NSScreen.screens
var pos = NSPoint()
pos.x = screens[0].visibleFrame.midX
pos.y = screens[0].visibleFrame.midY
self.view.window?.setFrameOrigin(pos)
self.view.window?.zoom(self)
self.view.window?.level = .floating
//self.view.window?.backgroundColor = NSColor.white
//stop the user from moving window
self.view.window?.isMovable = false
//disable resizable mode
self.view.window?.styleMask.remove(.resizable)
self.view.window?.setIsVisible(true)
}
//set up font for the reflectionForm
textView.font = NSFont.systemFont(ofSize: 30)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func saveTextViewString(){
if let documentDirectoryFileURL = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
let fileName = "savedText.txt"
let targetTextFilePath = documentDirectoryFileURL + "/" + fileName
do {
try ViewController.textViewString.write(toFile: targetTextFilePath, atomically: true, encoding: String.Encoding.utf8)
print("successfully recorded: \(ViewController.textViewString.description) at \(fileName.utf8CString)")
} catch let error as NSError {
print("failed to write: \(error)")
}
}
}
}
AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
//save the string in the textview into a text file
ViewController().saveTextViewString()
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
Thank you for the #jnpdx's comments, I was able to solve this by just declaring ViewController in the AppDelegate by stating var viewController: ViewController!
ViewController.swift
import Cocoa
class ViewController: NSViewController {
#IBOutlet var textView: NSTextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// start with hidden and show after moving to the main screen
DispatchQueue.main.async {
//keep the window top
self.view.window?.level = .floating
//set up the main display as the display where window shows up
let screens = NSScreen.screens
var pos = NSPoint()
pos.x = screens[0].visibleFrame.midX
pos.y = screens[0].visibleFrame.midY
self.view.window?.setFrameOrigin(pos)
self.view.window?.zoom(self)
self.view.window?.level = .floating
//self.view.window?.backgroundColor = NSColor.white
//stop the user from moving window
self.view.window?.isMovable = false
//disable resizable mode
self.view.window?.styleMask.remove(.resizable)
self.view.window?.setIsVisible(true)
}
//set up font for the reflectionForm
textView.font = NSFont.systemFont(ofSize: 30)
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func saveTextViewString(){
if let documentDirectoryFileURL = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last {
let fileName = "savedText.txt"
let targetTextFilePath = documentDirectoryFileURL + "/" + fileName
do {
try textView.string.write(toFile: targetTextFilePath, atomically: true, encoding: String.Encoding.utf8)
print("successfully recorded: \(textView.string.description) at \(fileName.utf8CString)")
} catch let error as NSError {
print("failed to write: \(error)")
}
}
}
}
AppDelegate.swift
import Cocoa
#main
class AppDelegate: NSObject, NSApplicationDelegate {
//connect viewController with ViewController
var viewController: ViewController!
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
//save the string in the textview into a text file
viewController.saveTextViewString()
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}

Presenting view controller from detached view controller is discouraged. Keeping User logged in issue

I'm trying to have the user move to automatically go to the Home Screen and not have to log in again. Basically, to remember the user. I used User Defaults to save the user login info and put the listener for the key in the viewDidLoad of the first login page. I used an if statement to switch the view controllers but it doesn't work and prints (Presenting view controller from detached view controller is discouraged).
LoginViewController:
import UIKit
import FirebaseAuth
import AVKit
class LoginViewController: UIViewController {
var videoPlayer:AVPlayer?
var videoPlayerLayer:AVPlayerLayer?
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var Back: UIButton!
#IBOutlet weak var passwordtextField: UITextField!
#IBOutlet weak var loginButton: UIButton!
#IBOutlet weak var errorLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
setupElements()
}
func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}
func setupElements(){
errorLabel.alpha = 0
Utilities.styleTextField(emailTextField)
Utilities.styleTextField(passwordtextField)
Utilities.styleFilledButton(loginButton)
}
func validateFields() -> String?
{
//make sure fields are filled
if emailTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == "" || passwordtextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""
{
return "Please fill all fields"
}
return nil
}
#IBAction func loginTapped(_ sender: Any) {
//creates a clean version of the text field
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordtextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let error = validateFields()
//sign in user
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
UserDefaults.standard.set(Auth.auth().currentUser!.uid, forKey: "user_uid_key")
UserDefaults.standard.synchronize()
if error != nil{
self.errorLabel.text = "Invalid Username/Password try again."
self.errorLabel.alpha = 1
}
else{
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
HomeViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
}
//make sure all fields are filled
}
override func viewWillAppear(_ animated: Bool) {
setUpVideo()
}
func setUpVideo(){
//Get path to resource bundle
let bundlePath = Bundle.main.path(forResource: "IMG_7211 2", ofType: "mov")
guard bundlePath != nil else{
return
}
//create the url from it
let url = URL(fileURLWithPath: bundlePath!)
//Create The video Player item
let item = AVPlayerItem(url: url)
//create the player
videoPlayer = AVPlayer(playerItem: item)
//create the layer
videoPlayerLayer = AVPlayerLayer(player: videoPlayer!)
//adjust the size and frame
videoPlayerLayer?.frame = CGRect(x: -self.view.frame.size.width*1.5, y:0, width: self.view.frame.size.width*4, height: self.view.frame.size.height)
view.layer.insertSublayer(videoPlayerLayer!, at: 0)
//add and play
videoPlayer?.playImmediately(atRate: 0.8)
}
}
ViewController:
import UIKit
import AVKit
import Firebase
import FirebaseAuth
class ViewController: UIViewController {
var videoPlayer:AVPlayer?
var videoPlayerLayer:AVPlayerLayer?
#IBOutlet weak var signUpButton: UIButton!
#IBOutlet weak var logInButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
if UserDefaults.standard.object(forKey: "user_uid_key") != nil {
print("i see u")
let navController = UINavigationController(rootViewController: HomeViewController())
navController.navigationBar.barStyle = .black
self.present(navController, animated: false, completion: nil)
}
else {
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
ViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
// Do any additional setup after loading the view.
setupElements()
}
func showhomepage() {
let homeViewController = self.storyboard?.instantiateViewController(identifier: Constants.StoryBoard.homeViewController) as?
HomeViewController
self.view.window?.rootViewController = homeViewController
self.view.window?.makeKeyAndVisible()
}
override func viewWillAppear(_ animated: Bool) {
//Set up video in background
setUpVideo()
}
func setUpVideo(){
//Get path to resource bundle
let bundlePath = Bundle.main.path(forResource: "Project", ofType: "mp4")
guard bundlePath != nil else{
return
}
//create the url from it
let url = URL(fileURLWithPath: bundlePath!)
//Create The video Player item
let item = AVPlayerItem(url: url)
//create the player
videoPlayer = AVPlayer(playerItem: item)
//create the layer
videoPlayerLayer = AVPlayerLayer(player: videoPlayer!)
//adjust the size and frame
videoPlayerLayer?.frame = CGRect(x: -self.view.frame.size.width*1.5, y:0, width: self.view.frame.size.width*4, height: self.view.frame.size.height)
view.layer.insertSublayer(videoPlayerLayer!, at: 0)
//add and play
videoPlayer?.playImmediately(atRate: 1)
}
func setupElements(){
Utilities.styleFilledButton(signUpButton)
Utilities.styleHollowButton(logInButton)
}
}
Looks like you're using Firebase. Do not store any login information in the User defaults. What you should do is create a blank view controller that will check if the user is signed in. If the user is signed it, it will present your HomeViewController; if the user is not signed in, it will present the login screen. You can also choose to perform these checks in your AppDelegate/SceneDelegate if you want to avoid the extra view controller.
The empty ViewController should be the initial/root ViewController.
You cannot present view controllers from inside viewDidLoad, use viewDidAppear.
Here is a basic example for the view controller way:
// in the new empty view controller, import FirebaseAuth
var handle: AuthStateDidChangeListenerHandle!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
handle = Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
// Go to Home Screen/ switch root
} else {
// Go to sign in screen/ switch root
}
}
}

How do I pass a scanned barcode ID from first view controller to second View Controller's UILabel?

This is the barcode scanning tutorial I used in my program, so that you have a lot more context when you read my code: Link
Here is what my program does so far: Essentially, when I scan an item's barcode with my phone, the UIAlert pops up with the barcode ID displayed and a button prompting the user to open the "Results" page. This is all fine and good, but how do I pass that same scanned barcode ID into a label on the Result's page? I have been stuck on this for 2 days now, even though it seems like such an easy task.
Any help is much appreciated <3
Here is my relevant code:
ProductCatalog.plist ->
Link to Image
Scanner_ViewController.swift (first View Controller) ->
import UIKit
import AVFoundation
class Scanner_ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ScannerDelegate
{
private var scanner: Scanner?
override func viewDidLoad()
{
super.viewDidLoad()
self.scanner = Scanner(withDelegate: self)
guard let scanner = self.scanner else
{
return
}
scanner.requestCaptureSessionStartRunning()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Mark - AVFoundation delegate methods
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
guard let scanner = self.scanner else
{
return
}
scanner.metadataOutput(output,
didOutput: metadataObjects,
from: connection)
}
// Mark - Scanner delegate methods
func cameraView() -> UIView
{
return self.view
}
func delegateViewController() -> UIViewController
{
return self
}
func scanCompleted(withCode code: String)
{
print(code)
showAlert_Success(withTitle: (code))
}
private func showAlert_Success(withTitle title: String)
{
let alertController = UIAlertController(title: title, message: "Product has been successfully scanned", preferredStyle: .alert)
// programatically segue to the next view controller when the UIAlert pops up
alertController.addAction(UIAlertAction(title:"Get Results", style: .default, handler:{ action in self.performSegue(withIdentifier: "toAnalysisPage", sender: self) }))
present(alertController, animated: true)
}
}
Scanner.Swift (accompanies Scanner_ViewController.swift)->
import Foundation
import UIKit
import AVFoundation
protocol ScannerDelegate: class
{
func cameraView() -> UIView
func delegateViewController() -> UIViewController
func scanCompleted(withCode code: String)
}
class Scanner: NSObject
{
public weak var delegate: ScannerDelegate?
private var captureSession : AVCaptureSession?
init(withDelegate delegate: ScannerDelegate)
{
self.delegate = delegate
super.init()
self.scannerSetup()
}
private func scannerSetup()
{
guard let captureSession = self.createCaptureSession()
else
{
return
}
self.captureSession = captureSession
guard let delegate = self.delegate
else
{
return
}
let cameraView = delegate.cameraView()
let previewLayer = self.createPreviewLayer(withCaptureSession: captureSession,
view: cameraView)
cameraView.layer.addSublayer(previewLayer)
}
private func createCaptureSession() -> AVCaptureSession?
{
do
{
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(for: .video) else
{
return nil
}
let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
let metaDataOutput = AVCaptureMetadataOutput()
// add device input
if captureSession.canAddInput(deviceInput) && captureSession.canAddOutput(metaDataOutput)
{
captureSession.addInput(deviceInput)
captureSession.addOutput(metaDataOutput)
guard let delegate = self.delegate,
let viewController = delegate.delegateViewController() as? AVCaptureMetadataOutputObjectsDelegate else
{
return nil
}
metaDataOutput.setMetadataObjectsDelegate(viewController,
queue: DispatchQueue.main)
metaDataOutput.metadataObjectTypes = self.metaObjectTypes()
return captureSession
}
}
catch
{
// handle error
}
return nil
}
private func createPreviewLayer(withCaptureSession captureSession: AVCaptureSession,
view: UIView) -> AVCaptureVideoPreviewLayer
{
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
return previewLayer
}
private func metaObjectTypes() -> [AVMetadataObject.ObjectType]
{
return [.qr,
.code128,
.code39,
.code39Mod43,
.code93,
.ean13,
.ean8,
.interleaved2of5,
.itf14,
.pdf417,
.upce
]
}
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
self.requestCaptureSessionStopRunning()
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let scannedValue = readableObject.stringValue,
let delegate = self.delegate
else
{
return
}
delegate.scanCompleted(withCode: scannedValue)
}
public func requestCaptureSessionStartRunning()
{
self.toggleCaptureSessionRunningState()
}
public func requestCaptureSessionStopRunning()
{
self.toggleCaptureSessionRunningState()
}
private func toggleCaptureSessionRunningState()
{
guard let captureSession = self.captureSession
else
{
return
}
if !captureSession.isRunning
{
captureSession.startRunning()
}
else
{
captureSession.stopRunning()
}
}
}
Analysis_ViewController.swift (second view controller) ->
Right now, the forKey: has been hard-coded to item ID 8710908501708 because I have no idea how to actually pass camera-scanned ID's into the second View Controller :/
import UIKit
class Analysis_ViewController: UIViewController
{
#IBOutlet weak var productTitle: UILabel!
func getData()
{
let path = Bundle.main.path(forResource:"ProductCatalog", ofType: "plist")
let dict:NSDictionary = NSDictionary(contentsOfFile: path!)!
if (dict.object(forKey: "8710908501708" as Any) != nil)
{
if let levelDict:[String : Any] = dict.object(forKey: "8710908501708" as Any) as? [String : Any]
{
// use a for loop to iterate through all the keys and values in side the "Levels" dictionary
for (key, value) in levelDict
{
// if we find a key named whatever we care about, we can print out the value
if (key == "name")
{
productTitle.text = (value as! String)
}
}
}
}
}
// listing the better options that are safer in comparison to the scanned product image
override func viewDidLoad()
{
super.viewDidLoad()
getData()
}
}
Do you have a variable to hold the scanned ID in your view controllers? If not, you can add var itemID: String? to both Scanner_ViewController and Analysis_ViewController.
Then in your func where you get the scanned code, you can set it to the variable.
func scanCompleted(withCode code: String) {
print(code)
itemID = code // Saves the scanned code to your var
showAlert_Success(withTitle: (code))
}
For passing data to another view controller via segue, you might want to look into this UIViewController method for segues: documentation here. This answer also might help.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAnalysisPage" {
if let viewController = segue.destination as? Analysis_ViewController {
viewController.itemID = itemID
}
}
}

MLMediaLibrary - Displaying the Photo Library -Swift Code Wrong - macOS

Trying to show photos from the Photo Library. The Apple Sample Code below does not work properly. It doesn't wait for the notifications but tries to launch the collection view which crashes at Number of Items in Section because there is no photo data to show.
Thanks for any help from you Swift guru's out there!
/*
Copyright (C) 2016 Apple Inc. All Rights Reserved.
See LICENSE.txt for this sample’s licensing information
Abstract:
The `ViewController` class is a subclass to NSViewController responsible for managing the app's content.
*/
import Cocoa
import MediaLibrary
class IconViewBox : NSBox {
override func hitTest(_ aPoint: NSPoint) -> NSView? {
// Don't allow any mouse clicks for subviews in this NSBox.
return nil
}
}
// MARK: -
class ViewController: NSViewController, NSCollectionViewDelegate {
// MARK: - Types
// Keys describing the dictionary for each photo loaded.
private struct ItemKeys {
static let imageKey = "icon"
static let nameKey = "name"
}
// MLMediaLibrary property values for KVO.
private struct MLMediaLibraryPropertyKeys {
static let mediaSourcesKey = "mediaSources"
static let rootMediaGroupKey = "rootMediaGroup"
static let mediaObjectsKey = "mediaObjects"
static let contentTypeKey = "contentType"
}
// MARK: - Properties
/**
The KVO contexts for `MLMediaLibrary`.
This provides a stable address to use as the `context` parameter for KVO observation methods.
*/
private var mediaSourcesContext = 1
private var rootMediaGroupContext = 2
private var mediaObjectsContext = 3
private var photoSize = CGSize(width: 168, height: 145)
// Contains an array of dictionaries describing each photo (refer to ItemKeys for key/values).
#IBOutlet weak var arrayController: NSArrayController!
#IBOutlet weak var collectionView: NSCollectionView!
#IBOutlet private weak var noPhotosLabel: NSTextField!
#IBOutlet private weak var activityIndicator: NSProgressIndicator!
// MLMediaLibrary instances for loading the photos.
private var mediaLibrary: MLMediaLibrary!
private var mediaSource: MLMediaSource!
private var rootMediaGroup: MLMediaGroup!
// MARK: - View Controller Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
print ("VDL1...................")
// Start progress indicator in case fetching the photos from the photo library takes time.
self.activityIndicator.isHidden = false
self.activityIndicator.startAnimation(self)
self.collectionView.minItemSize = self.photoSize
self.collectionView.maxItemSize = self.photoSize
self.arrayController.setSelectionIndex(-1) // No selection to start out with.
// Setup the media library to load only photos, don't include other source types.
let options: [String : AnyObject] =
[MLMediaLoadSourceTypesKey: MLMediaSourceType.image.rawValue as AnyObject,
MLMediaLoadIncludeSourcesKey: [MLMediaSourcePhotosIdentifier, MLMediaSourceiPhotoIdentifier] as AnyObject]
// Create our media library instance to get our photo.
mediaLibrary = MLMediaLibrary(options: options)
// We want to be called when media sources come in that's available (via observeValueForKeyPath).
self.mediaLibrary.addObserver(self,
forKeyPath: MLMediaLibraryPropertyKeys.mediaSourcesKey,
options: NSKeyValueObservingOptions.new,
context: &mediaSourcesContext)
if (self.mediaLibrary.mediaSources != nil) {
print ("VDL2...................")
} // Reference returns nil but starts the asynchronous loading.
}
deinit {
// Make sure to remove us as an observer before "mediaLibrary" is released.
self.mediaLibrary.removeObserver(self, forKeyPath: MLMediaLibraryPropertyKeys.mediaSourcesKey, context:&mediaSourcesContext)
}
// MARK: - NSCollectionViewDataSource
func numberOfSectionsInCollectionView(_ collectionView: NSCollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
let photos = self.arrayController.arrangedObjects as! NSArray
return photos.count
}
func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAtIndexPath indexPath: IndexPath) -> NSCollectionViewItem {
let item = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "IconItem"), for:indexPath)
let photos = self.arrayController.arrangedObjects as! NSArray
let iconInfo = photos[(indexPath as NSIndexPath).item]
item.representedObject = iconInfo
return item
}
// MARK: - NSCollectionViewDelegate
func collectionView(_ collectionView: NSCollectionView, didSelectItemsAt indexPaths: Set<IndexPath>) {
if let itemIndexPath = indexPaths.first {
let photos = self.arrayController.arrangedObjects as! NSArray
let itemDict = photos[((itemIndexPath as NSIndexPath).item)] as! NSDictionary
if let itemTitle = itemDict[ItemKeys.nameKey] as? String {
if (itemTitle.characters.count > 0) {
print("selected photo: '\(itemTitle)'")
}
else {
print("selected photo: <no title>")
}
}
}
}
// MARK: - Utilities
/// Helps to make sure the media object is the photo format we want.
private func isValidImage(_ mediaObject: MLMediaObject) -> Bool {
var isValidImage = false
let attrs = mediaObject.attributes
let contentTypeStr = attrs[MLMediaLibraryPropertyKeys.contentTypeKey] as! String
// We only want photos, not movies or older PICT formats (PICT image files are not supported in a sandboxed environment).
if ((contentTypeStr != kUTTypePICT as String) && (contentTypeStr != kUTTypeQuickTimeMovie as String))
{
isValidImage = true
}
return isValidImage
}
/// Obtains the title of the MLMediaObject (either the meta name or the last component of the URL).
func imageTitle(_ fromMediaObject: MLMediaObject) -> String {
guard let title = fromMediaObject.attributes["name"] else {
return fromMediaObject.url!.lastPathComponent
}
return title as! String
}
// MARK: - Photo Loading
/// Observer for all key paths returned from the MLMediaLibrary.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print ("Observe1...................")
if (keyPath == MLMediaLibraryPropertyKeys.mediaSourcesKey && context == &mediaSourcesContext && object! is MLMediaLibrary) {
// The media sources have loaded, we can access the its root media.
if let mediaSource = self.mediaLibrary.mediaSources?[MLMediaSourcePhotosIdentifier] {
self.mediaSource = mediaSource
}
else if let mediaSource = self.mediaLibrary.mediaSources?[MLMediaSourceiPhotoIdentifier] {
self.mediaSource = mediaSource
}
else {
print ("Observe2...................")
// Can't find any media sources.
self.noPhotosLabel.isHidden = false
// Stop progress indicator.
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimation(self)
return // No photos found.
}
// Media Library is loaded now, we can access mediaSource for photos
self.mediaSource.addObserver(self,
forKeyPath: MLMediaLibraryPropertyKeys.rootMediaGroupKey,
options: NSKeyValueObservingOptions.new,
context: &rootMediaGroupContext)
// Obtain the media grouping (reference returns nil but starts asynchronous loading).
if (self.mediaSource.rootMediaGroup != nil) {}
}
else if (keyPath == MLMediaLibraryPropertyKeys.rootMediaGroupKey && context == &rootMediaGroupContext && object! is MLMediaSource) {
print ("Observe3...................")
// The root media group is loaded, we can access the media objects.
// Done observing for media groups.
self.mediaSource.removeObserver(self, forKeyPath: MLMediaLibraryPropertyKeys.rootMediaGroupKey, context:&rootMediaGroupContext)
self.rootMediaGroup = self.mediaSource.rootMediaGroup
self.rootMediaGroup.addObserver(self,
forKeyPath: MLMediaLibraryPropertyKeys.mediaObjectsKey,
options: NSKeyValueObservingOptions.new,
context: &mediaObjectsContext)
// Obtain the all the photos, (reference returns nil but starts asynchronous loading).
if (self.rootMediaGroup.mediaObjects != nil) {}
}
else if (keyPath == MLMediaLibraryPropertyKeys.mediaObjectsKey && context == &mediaObjectsContext && object! is MLMediaGroup) {
print ("Observe4...................")
// The media objects are loaded, we can now finally access each photo.
// Done observing for media objects that group.
self.rootMediaGroup.removeObserver(self, forKeyPath: MLMediaLibraryPropertyKeys.mediaObjectsKey, context:&mediaObjectsContext)
// Stop progress indicator since we know if we have photos (or not).
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimation(self)
let mediaObjects = self.rootMediaGroup.mediaObjects
if (mediaObjects != nil && mediaObjects!.count > 0) {
print ("Observe5...................")
// Add photos to the array, to be used in our NSCollectionView.
for mediaObject in mediaObjects! {
if (self.isValidImage(mediaObject)) { // Make sure the media object is a photo.
let title = self.imageTitle(mediaObject)
if let image = NSImage.init(contentsOf: mediaObject.thumbnailURL!) {
let iconItem : Dictionary = [ItemKeys.imageKey: image, ItemKeys.nameKey: title] as [String : Any]
self.arrayController.addObject(iconItem)
}
}
}
self.collectionView.reloadData()
}
else {
// No photos available.
self.noPhotosLabel.isHidden = false
}
self.rootMediaGroup = nil // We are done with this.
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}
NSCollectionViewDataSource delegate is missing
class ViewController: NSViewController, NSCollectionViewDelegate, NSCollectionViewDataSource
Next you need to fix these methods: 'numberOfSectionsInCollectionView' has been renamed to 'numberOfSections(in:)' 'collectionView(:itemForRepresentedObjectAtIndexPath:)' has been renamed to 'collectionView(:itemForRepresentedObjectAt:)'

stopAnimating removes the current image

An oldish retired DEC PDP 8 assembly programmer needs another set of eyes. I created a Hello World program that displays images of the family. The animation works fine, but I would like the user to be able to stop the animation so I added a button to “Pause” the image so that the user can view it at their leisure. However, the .stopAnimation function clears the view. How can this be prevented. Please be kind.
import UIKit
class FamilyUnitController: UIViewController {
#IBOutlet weak var familyMember: UILabel!
#IBOutlet weak var familyPhotos: UIImageView!
var familyName: String = ""
var familyUnitArray = [String]()
#IBOutlet weak var pauseButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
enter code here
configureImageView()
}
#IBAction func pauseImage(_ sender: UIButton) {
if familyPhotos.isAnimating {
familyPhotos.stopAnimating()
pauseButton.setTitle("Paused", for: [])
} else {
familyPhotos.startAnimating()
pauseButton.setTitle("Pause", for: [])
}
}
func configureImageView() {
familyMember.text = familyUnitArray[0]
familyMember.sizeToFit()
let imagePrefix = familyUnitArray[1]
print ("configureImageView called...")
let maxImageCnt = 10
if let imageView = familyPhotos {
print ("Processing images...")
let fileManager = FileManager.default
let path = Bundle.main.resourcePath!
print (#function,">Path> ",path)
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path)!
var imageArr = [UIImage]()
var imageCnt: Int = 0
while let element = enumerator.nextObject() as? String {
print (#function,"> element.description",element.debugDescription)
let tmpStr = element.lowercased()
let tmpImagePrefix = imagePrefix.lowercased()
if (element.hasSuffix("png") || element.hasSuffix("jpg") || element.hasSuffix("jpeg"))
&& tmpStr.hasPrefix(tmpImagePrefix)
&& imageCnt < maxImageCnt {
let elementWithPath = path+"/"+element
imageArr.append(UIImage(contentsOfFile: elementWithPath)!)
imageCnt += 1
}
}
imageView.isUserInteractionEnabled = true
imageView.animationImages = imageArr
imageView.contentMode = .scaleAspectFit
imageView.backgroundColor = UIColor.white
imageView.animationDuration = 15
imageView.startAnimating()
}
}
}
You may refer here to for pausing the animation / not stopping it.
Is there a way to pause a CABasicAnimation?
In your case ,the code should be like the following, you may need to change a little to make it perfect.
#IBAction func pauseImage(_ sender: UIButton) {
if familyPhotos.layer.speed != 0 {
let time = familyPhotos.layer.convertTime(CACurrentMediaTime(), to: nil)
familyPhotos.layer.speed = 0
familyPhotos.layer.timeOffset = time
pauseButton.setTitle("Paused", for: [])
} else {
familyPhotos.layer.beginTime = familyPhotos.layer.timeOffset - CACurrentMediaTime()
familyPhotos.layer.timeOffset = 0
familyPhotos.layer.speed = 1
pauseButton.setTitle("Pause", for: [])
}
}