How did Apple implement the BookMark feature on its iOS Books app? - ios

I am making a PDF Reader similar to the Books App built in on iOS. My bookmarks work and update, except when I exit the PDF View and go back to document browsing (file picker), then open my PDF again. Then my app crashes:
private func updateBookmarkStatus() {
if let documentURL = pdfDocument?.documentURL?.absoluteString,
let bookmarks = UserDefaults.standard.array(forKey: documentURL) as? [Int],
let currentPage = pdfView.currentPage,
let index = pdfDocument?.index(for: currentPage) {
/*crashes on this line*/: bookmarkButton.image = bookmarks.contains(index) ? #imageLiteral(resourceName: "Bookmark-P") : #imageLiteral(resourceName: "Bookmark-N")
} else {
print("Bookmark Error")
}
}
with
Unexpectedly found nil while implicitly unwrapping an Optional value:
The line that I put the comment on just updates the bookmark to the "filled version", showing the user that the page is bookmarked.
The bookmark updates and works within the PDF. It is once I go out of the PDF, to the document browsing, then go back into the PDF that it crashes. It might be worth noting that "Bookmark Error" gets printed when I initially open the PDF for the first time.
If I do not touch the bookmark button, then I can go to document browsing and reopen the PDF as many times as I want. But as soon as I touch the bookmark button, I cannot re-open my PDF file again.
Any suggestions?
Thanks in advance.

Related

iOS Action Extension, share PDF from Safari fails

I have an Action Extension to which I'm trying to share PDF-files.
I'm using the boilerplate code for ActionRequestHandler.swift that was autogenerated for me:
func beginRequest(with context: NSExtensionContext) {
// Do not call super in an Action extension with no user interface
self.extensionContext = context
for item in context.inputItems as! [NSExtensionItem] {
if let attachments = item.attachments {
for itemProvider in attachments {
...
...
}
}
}
}
Working from other apps
When exporting from every application except Safari, this is what I get:
This is all ok, I can verify that it's an pdf by checking the com.adobe.pdf and then I use the public.file-url to fetch the shared file.
Failing from Safari
But when exporting from Safari (doesn't matter if I choose "Automatic" or "Pdf" for file type), I instead only get com.apple.property-list:
Further info
Both dropbox and OneDrive works, so it's doable in some sort of way.
Also I realised that sharing an PDF from a url that's protected by some sort of login doesn't work with "Public.file-url" since that URL wont be accessible from inside swift-code.
That leads me to think that the java-script preprocessor might be the way to go? Fetch the pdf-contents with JS and pass it on to code?
Question
How do I use the com.apple.property-list to fetch the file?
Or is some config I did faulty, since I get this property-list instead of the pdf/url combo?
While I didn't manage to figure out a solution to the original question, I did manage to solve the problem.
When adding an Action Extension, one gets to choose Action type:
Presents user interface
No user interface
I choosed No user interfacesince that was what I wanted.
That gave me an Action.js file and ActionRequestHandler.swift:
class ActionRequestHandler: NSObject, NSExtensionRequestHandling {
...
}
These files seem to work around a system where the Action.js is supposed to fetch/manipulate the source page and then send information to the backing Swift code. As stated in my original question, when sharing a PDF from Safari, no PDF-URL gets attached.
A working solution
If I instead choose Presents user interface, I got another setup, ActionViewController.swift:
class ActionViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
for item in self.extensionContext!.inputItems as! [NSExtensionItem] {
for provider in item.attachments! {
if provider.hasItemConformingToTypeIdentifier(kUTTypePDF as String) {
provider.loadItem(forTypeIdentifier: kUTTypePDF as String, options: nil, completionHandler: { (pdfUrl, error) in
OperationQueue.main.addOperation {
if let pdfUrl = pdfUrl as? URL {
// pdfUrl now contains the path to the shared pdf data
}
}
}
}
}
This file / solution works as expected, the extensionContext gets populated with one attachment that conforms to kUTTypePDF as expected.
Why this works, while the "no gui"-approach doesn't, I have no idea. Bug or feature?
I have not found any documentation of how/why this is supposed to work in Apple's developer section, the "share extension" documentation is very light.

How to stop bookmark from crashing PDF app?

I have a PDF Reader app using PDFKit with iOS 13 Swift, which has a bookmark button that can bookmark a particular page. It works fine for me while I am still looking at my PDF, but if I bookmark at least 1 page in my PDF and go back to my file browser, and pick my PDF again, the app crashes.
Here is my code:
private func updateBookmarkStatus() {
if let documentURL = pdfDocument?.documentURL?.absoluteString,
let bookmarks = UserDefaults.standard.array(forKey: documentURL) as? [Int],
let currentPage = pdfView.currentPage,
let index = pdfDocument?.index(for: currentPage) {
bookmarkButton.image = bookmarks.contains(index) ? #imageLiteral(resourceName: "Bookmark-P") : #imageLiteral(resourceName: "Bookmark-N")
}
}
and it crashes with
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This update function is called upon loading each separate page of the PDF. However, when I exit and enter my PDF again, it crashes here because I think Swift can't find the array of bookmarks since at least one has been marked. My app only crashes if I mark at least 1 bookmark, then exit to my file browser, then open my PDF again.
Any help is greatly appreciated.

XCODE / IOS - Share audio file to whatsapp with a direct opening

I am trying to share an audio file from my ios app to whatsapp, but with a direct opening of whatsapp, and not an opening of the sharing menu with all the tiles.
Here is what i have now:
// Getting the original file
let fileName = #MY FILE NAME#
let filePath = Bundle.main.path(forResource: fileName, ofType: "mp3")!
let urlData = URL.init(fileURLWithPath: filePath)
let nsData = NSData(contentsOf: urlData)
if (nsData != nil){
// Creating the temporary file to share in the accessible ressources
let newFileName = "file.mp3"
let newFilePath = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/\(newFileName)"
nsData?.write(toFile: newFilePath, atomically: true)
let newUrlData = URL.init(fileURLWithPath: newFilePath)
// Sharing the file to whatsapp
// Possibility 1 (does not work yet)
// let documentController = UIDocumentInteractionController(url: newUrlData)
// documentController.uti = "net.whatsapp.audio"
// documentController.presentOpenInMenu(from: CGRect.zero, in: self.view, animated: true)
// Possibility 2 (works only with the sharing menu)
let activityVC = UIActivityViewController(activityItems: [NSURL(fileURLWithPath: newFilePath)], applicationActivities: nil)
self.present(activityVC, animated: true, completion: nil)
}
As I do this, sharing an audio file to whatsapp works, but it first open the sharing menu, with the messenger tile, message tile, notes tile, ... (and it doesn't works for the messenger app). In the end I would like to be able to share on messenger AND whatsapp.
As explicated here in the whatsapp documentation, I want to open directly the whatsapp application when I try to share the file:
Alternatively, if you want to show only WhatsApp in the application list (instead of WhatsApp plus any other public/*-conforming apps) you can specify a file of one of aforementioned types saved with the extension that is exclusive to WhatsApp:
images - «.wai» which is of type net.whatsapp.image
videos - «.wam» which is of type net.whatsapp.movie
audio files - «.waa» which is of type net.whatsapp.audio
When triggered, WhatsApp will immediately present the user with the contact/group picker screen. The media will be automatically sent to a selected contact/group.
So I tried to change the line :
let newFileName = "file.mp3"
To one of these :
let newFileName = "file.mp3.waa"
let newFileName = "file.waa"
let newFileName = "file.waa.mp3"
But it still shows the same sharing menu (and can't read the audiofile if it ends with the .waa extension).
-> 1) Is it possible to do what I want to do ?
-> 2) If not, is there a way to share to messenger & whatsapp with the same code keeping one sharing menu
-> 3) If not, is there a way to reduce the sharing menu to only one tile depending on different calling event, so there is no confusing choosing of tiles
Thanks,
Antoine
Cf: XCODE / IOS - How to use exclusive extension to immediately present whatsapp (.wai, .waa, .wam)
FYI: As I went through a lot of tests with this, I couldn't find any solution yet.
Whatsapp recognize the file extension, but cannot even read it. Once shared, when you click on it, it's written ".whatsapp audio file", nothing more (And it's not even shared directly).
I sent a email to whatsapp developper team, they said they have others problem to fix currently, so it's not even on their to do list.
Wait & see..

Spotify iOS SDK returning songs that do not exist? - Swift

So I'm building some playlist and song retrieval into my app at the moment, and I'm really confused by some of the results I'm getting back from the API. It seems to be returning songs that no longer exist on Spotify or have been long removed from a playlist.
Retrieving a list of Playlists from a user is working fine, but just in case this problem is arising from the way I draw that playlist's tracks, here is the code I use to get them:
SPTPlaylistSnapshot.playlistWithURI(uri, accessToken: session.accessToken) { (error, playlistSnapshotOb) -> Void in
if let playlistSnapshot = playlistSnapshotOb as? SPTPlaylistSnapshot {
let itemz = playlistSnapshot.firstTrackPage.items //tracksForPlayback()
for item in itemz{
let track = item as! SPTPlaylistTrack
let splice = "\(track.uri)"
let trackURI = splice.stringByReplacingOccurrencesOfString("spotify:track:", withString: "")
var displayArtist = String()
let artistz = track.artists
if artistz.count > 1{
for i in 0...(artistz.count - 1){
let itz = artistz[i] as! SPTPartialArtist
if i > 0 {
displayArtist += ", \(itz.name)"
}else{
displayArtist += "\(itz.name)"
}
}
self.tracks.append(track.name)
self.ArtistObjects.append(displayArtist)
self.uriS.append(trackURI)
}else{
let singularArtist = artistz[0] as! SPTPartialArtist
displayArtist = singularArtist.name
self.tracks.append(track.name)
self.ArtistObjects.append(displayArtist)
self.uriS.append(trackURI)
}
Additionally, below is a screenshot of the desktop Spotify app showing the real content of the playlist I am pulling:
Spotify per Desktop
You'll see that the songs "Big Bank Dank" and "Light Day Remix" are not actually on this playlist, but for some reason, on my app below, when I pull this playlist, it has these songs listed:
Spotify In My App
(Apparently I can't post an actual image because of my rep - apologies)
Any idea why it's doing this?
The tracks are probably just not available any longer for some unspecified reason. This is quite common. By default, the Spotify client does not show unavailable tracks in playlists, but in settings there is a toggle you can flip so that they are shown as greyed out instead.
I don't know about iOS SDK, but there should be either an attribute telling you the available markets for the tracks or if it is playable or not, depending on the country of the user being logged in.
This is how it works in the Web API, which should be similar.
https://developer.spotify.com/web-api/track-relinking-guide/

How to check a file is video or image?

I am developing a chat app using parse. I want to play the vodeo when users click on the video message and Display expandable image when the user click on the picture message. for that I need to differentiate image and video. Kindly guide me to do that...
Surely the easiest way will be of course to look at the file extension...?
For future googlers ... on didTapMessageBubbleAtIndexPath delegate you should check for item class
let message = yourMessageArray[indexPath.item]
if message.isMediaMessage() {
if message.media().isKindOfClass(JSQPhotoMediaItem) {
//Handle image
} else if message.media().isKindOfClass(JSQVideoMediaItem) {
let video = message.media() as! JSQVideoMediaItem
let videoURL = video.fileURL
}
}
Save this information in another field on Parse during the asset upload.

Resources