I've been trying to figure how how to detect the url I retrieve from Firebase Storage is a Photo or a Video,
I followed this question but it doesn't seem to work with Firebase Storage URLs.
The solution implemented in that question work only when the url has the extension at the end.
This is the code I implemented:
extension String {
public func isImageType() -> Bool {
// image formats which you want to check
let imageFormats = ["jpg", "png", "gif"]
if URL(string: self) != nil {
let extensi = (self as NSString).pathExtension
return imageFormats.contains(extensi)
}
return false
}
}
To be clear, URLs retrieved from Firebase have this form.
Photo url example:
https://firebasestorage.googleapis.com/v0/b/myApp-1e48d.appspot.com/o/Images%2FD118DA58-C128-4E5E-BF24-AA820BEE5590.jpg?alt=media&token=49eie236-f9d1-45f8-bd0b-887382c61ccd
Video url example:
https://firebasestorage.googleapis.com/v0/b/myApp-1e48d.appspot.com/o/Videos%2F1E4B7CA0-4D0E-4AC5-9856-2F59D0811C47.mp4?alt=media&token=615teacf-0d20-48aa-bb8f-cew84a14d76d
As you can see the extension is not at the end (because there is the need of the token), hence the extension methods linked before doesn't work.
How can I still understand if the url retrieved from Firebase is of a Photo or a Video?
You can simply initialize an url with your link and get the path extension:
let link1 = "https://firebasestorage.googleapis.com/v0/b/myApp-1e48d.appspot.com/o/Images%2FD118DA58-C128-4E5E-BF24-AA820BEE5590.jpg?alt=media&token=49eie236-f9d1-45f8-bd0b-887382c61ccd"
if let url = URL(string: link1) {
let fileType = url.pathExtension // "jpg"
}
let link2 = "https://firebasestorage.googleapis.com/v0/b/myApp-1e48d.appspot.com/o/Videos%2F1E4B7CA0-4D0E-4AC5-9856-2F59D0811C47.mp4?alt=media&token=615teacf-0d20-48aa-bb8f-cew84a14d76d"
if let url = URL(string: link2) {
let fileType = url.pathExtension // "mp4"
}
I implemented a code that works:
First Remove all after "?":
extension String {
public func removeAfterExtension() -> String?{
if let index = (self.range(of: "?")?.lowerBound){
let beforeEqualsTo = String(self.prefix(upTo: index))
return beforeEqualsTo
}else{
return self
}
}
Then check if it is image Type:
public func isImageType() -> Bool {
// image formats which you want to check
let imageFormats = ["jpg", "png", "gif"]
if URL(string: self) != nil {
let extensi = (self as NSString).pathExtension
return imageFormats.contains(extensi)
}
return false
}
Related
I am currently working on a safari app extension that blocks content. I want the user to configure the rule (turning a rule on and off). Since I can’t overwrite the bundled JSON files and we can’t write to the documents folder, as it’s not accessible to the extension I decided to use App Groups. My approach looks like this:
Within the ContentBlockerRequestHandler I want to save the blockerList.json into the app group (Only when launched for the first time)
When this is done I want that the handler reads from the app group by taking the url of my json which is within the app group instead of taking the default json in the extension
Since I can not debug the handler I don't know if I am on the right path. The following shows my code:
class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
guard let rulesUrl = loadRules() else {
let clonedRules = cloneBlockerList()
save(rules: clonedRules)
return
}
guard let attachment = NSItemProvider(contentsOf: rulesUrl) else { return }
let item = NSExtensionItem()
item.attachments = [attachment]
context.completeRequest(returningItems: [item], completionHandler: nil)
}
private func cloneBlockerList() -> [Rule] {
var rules: [Rule] = []
if let url = Bundle.main.url(forResource: "blockerList", withExtension: "json") {
do {
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
let jsonData = try decoder.decode(ResponseData.self, from: data)
rules = jsonData.rules
} catch {
print("error:(error)")
}
}
return rules
}
private func save(rules: [Rule]) {
let documentsDirectory = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "my group identifier")
let archiveURL = documentsDirectory?.appendingPathComponent("rules.json")
let encoder = JSONEncoder()
if let dataToSave = try? encoder.encode(rules) {
do {
try dataToSave.write(to: archiveURL!)
} catch {
// TODO: ("Error: Can't save Counters")
return;
}
}
}
private func loadRules() -> URL? {
let documentFolder = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "my group identifier")
guard let jsonURL = documentFolder?.appendingPathComponent("rules.json") else {
return nil
}
return jsonURL
}
}
Thankful for any help
i have tried everything on internet to add a PDFViewer in my app. im working with ios 12. im asking you to help me understand what is the possible ways to add a pdf and a solution that can solve it in a easy way for my low experience with swift coding. thank you
We can use our native UIDocumentInteractionController for the same.
Follow below steps :
Step 1
var documentInteractionController = UIDocumentInteractionController()
Step 2
self.documentInteractionController.delegate = self
Step 3
func openDocument(atURL url: URL, screenTitle: String) {
self.documentInteractionController.url = url
self.documentInteractionController.name = screenTitle
self.documentInteractionController.delegate = self
self.documentInteractionController.presentPreview(animated: true)
}
Step 4 : Implement UIDocumentInteractionControllerDelegate
extension ViewController: UIDocumentInteractionControllerDelegate {
// when a document interaction controller needs a view controller for presenting a document preview.
func documentInteractionControllerViewControllerForPreview(_ controller: UIDocumentInteractionController) -> UIViewController {
return self.navigationController ?? UIViewController()
}
}
Some helper methods :
a) View Pdf
func viewPdf(urlPath: String, screenTitle: String) {
// open pdf for booking id
guard let url = urlPath.toUrl else {
print("Please pass valid url")
return
}
self.downloadPdf(fileURL: url, screenTitle: screenTitle) { localPdf in
if let url = localPdf {
DispatchQueue.main.sync {
self.openDocument(atURL: url, screenTitle: screenTitle)
}
}
}
}
b) function for download file
// method for download pdf file
func downloadPdf(fileURL: URL, screenTitle: String, complition: #escaping ((URL?) -> Void)) {
// Create destination URL
if let documentsUrl: URL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let destinationFileUrl = documentsUrl.appendingPathComponent("\(screenTitle).pdf")
if FileManager.default.fileExists(atPath: destinationFileUrl.path) {
try? FileManager.default.removeItem(at: destinationFileUrl)
}
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig)
let request = URLRequest(url: fileURL)
let task = session.downloadTask(with: request) { tempLocalUrl, response, error in
if let tempLocalUrl = tempLocalUrl, error == nil {
// Success
if let statusCode = (response as? HTTPURLResponse)?.statusCode {
print("Successfully downloaded. Status code: \(statusCode)")
}
do {
try FileManager.default.copyItem(at: tempLocalUrl, to: destinationFileUrl)
complition(destinationFileUrl)
} catch let writeError {
print("Error creating a file \(destinationFileUrl) : \(writeError)")
}
} else {
print("Error took place while downloading a file. Error description: \(error?.localizedDescription ?? "N/A")")
}
}
task.resume()
} else {
complition(nil)
}
}
here I am Downloading PDF and store on in File And Open That file in Quick Look
Here I am sharing screen
enter image description here
Reference link: https://www.hackingwithswift.com/example-code/libraries/how-to-preview-files-using-quick-look-and-qlpreviewcontroller
If you just need to present the PDF, you could use a WebView from WebKit and pass the data using the mimetype application/pdf.
like this:
webView.load(data, mimeType: "application/pdf", characterEncodingName: "UTF-8", baseURL: baseURL)
I have been trying to figure this out all day and yesterday night, but no luck. I can confirm that the LinkShareContent works but when I try to share a video file. It gives me an error code "reserved" but nothing else.
This is the code for the link
var content = LinkShareContent(url: URL(string: "https://google.com")!)
showShareDialog(content)
and this is the code for the video that does not work at all.
let video = Video(url: url)
var content = VideoShareContent(video: video, previewPhoto: Photo(image: inProgressItem.firstImage, userGenerated: true))
showShareDialog(content)
This will show the share Sheet on the controller
Func showShareDialog<C: ContentProtocol>(_ content: C, mode: ShareDialogMode = .shareSheet) {
let dialog = ShareDialog(content: content)
dialog.presentingViewController = self
dialog.mode = mode
do{
try dialog.show()
}
catch (let error){
print(error)
}
}
I have confirmed that the video is on the local path and I'm testing the app on iPhone 8 11.1.2
Had exactly the same issue. It was working for LinkShareContent but didn't work for VideoShareContent.
The solution:
Make sure you are getting the right URL for the video. The right one is the URL for key "UIImagePickerControllerReferenceURL" from info dictionary that comes from UIImagePickerController delegate method.
Working Code:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
picker.dismiss(animated: true)
if let videoURL = info["UIImagePickerControllerReferenceURL"] as? URL {
let video = Video(url: videoURL)
let content = VideoShareContent(video: video)
let dialog = ShareDialog(content: content)
dialog.failsOnInvalidData = true
dialog.mode = .native
dialog.presentingViewController = self
do {
try dialog.show()
} catch {
print(error)
}
}
}
Extra info: initially I did not use this key "UIImagePickerControllerReferenceURL" cuz it's deprecated. Apple advises using UIImagePickerControllerPHAsset instead. But the URL from there also returns reserved error. Another try was to use key "UIImagePickerControllerMediaURL", but it also didn't succeed.
I use PHPickerViewController instead of UIPickerController.
private lazy var videoPickerController: PHPickerViewController = {
let photoLibrary = PHPhotoLibrary.shared()
var configuration = PHPickerConfiguration(photoLibrary: photoLibrary)
configuration.selectionLimit = 1
configuration.filter = .any(of: [.videos])
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
return picker
}()
Then using PHAsset for initialisation ShareVideo(videoAsset:).
private func facebookShare(content: Content) {
guard let schemaUrl = URL(string: "fb://") else {
return
}
if UIApplication.shared.canOpenURL(schemaUrl) {
let video = ShareVideo(videoAsset: content)
let content = ShareVideoContent()
content.video = video
let dialog = ShareDialog(
viewController: self,
content: content,
delegate: self
)
do {
try dialog.validate()
} catch let error as NSError {
presentAlert(message: (error.userInfo[ErrorDeveloperMessageKey] as? String) ?? error.localizedDescription)
} catch {
presentAlert(message: error.localizedDescription)
}
if dialog.canShow {
dialog.show()
}
} else {
presentAlert(message: "FB app not installed")
}
}
And PHPickerViewControllerDelegate looks something like this (I always select only 1 asset that's why I use fetchResult.firstObject)
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let identifiers = results.compactMap(\.assetIdentifier)
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: identifiers, options: nil)
guard let videoAsset = fetchResult.firstObject else { return }
}
This solution works for iOS 14 and higher and if on your device Facebook app installed.
Also before upload video I login via FB.
I'm trying to take a screenshot of webpage but the image is always blank(white).
I'm using this code to convert CALayer to Data(taken from here)
extension CALayer {
/// Get `Data` representation of the layer.
///
/// - Parameters:
/// - fileType: The format of file. Defaults to PNG.
/// - properties: A dictionary that contains key-value pairs specifying image properties.
///
/// - Returns: `Data` for image.
func data(using fileType: NSBitmapImageFileType = .PNG, properties: [String : Any] = [:]) -> Data {
let width = Int(bounds.width * self.contentsScale)
let height = Int(bounds.height * self.contentsScale)
let imageRepresentation = NSBitmapImageRep(bitmapDataPlanes: nil, pixelsWide: width, pixelsHigh: height, bitsPerSample: 8, samplesPerPixel: 4, hasAlpha: true, isPlanar: false, colorSpaceName: NSDeviceRGBColorSpace, bytesPerRow: 0, bitsPerPixel: 0)!
imageRepresentation.size = bounds.size
let context = NSGraphicsContext(bitmapImageRep: imageRepresentation)!
render(in: context.cgContext)
return imageRepresentation.representation(using: fileType, properties: properties)!
}
}
And then to write the data to file as .png
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
{
let d = web.layer?.data() as NSData? //web is the instance of WKWebView
d!.write(toFile: "/Users/mac/Desktop/web.png", atomically: true)
}
But I'm getting a blank(white) png instead of what I expected
1). What am I doing wrong?
2). Is there any other possible ways to get the image representation of
webpage(Using swift)?
Thank you!
Latest Update:
Now you can take screenshot of WKWebView just like WebView.
Apple added new method for both iOS and macOS,
func takeSnapshot(with snapshotConfiguration: WKSnapshotConfiguration?,
completionHandler: #escaping (NSImage?, Error?) -> Void)
But its still in beta.
You can't take a screenshot of WKWebView. It always returns a blank image. Even if you try to include WKWebView inside another NSView and take a screenshot, it will give you blank image in place of WKWebView.
You should use WebView instead of WKWebView for your purpose. Check this question.
If you are ok with using private frameworks(apple doesn't allow your app in its store), check this GitHub. Its written in Obj-C. I don't know Obj-C so I can't explain what's happening in that code. But it claims to do the work.
Your best approach is to use WebView and use your mentioned extension data() on the WebView.
Just a question: Why don't you use phantomJS?
PS. Sorry for the late reply. I didn't see your e-mail.
Update: Swift 5 adding to prior.
I didn't want controls in the output, so I bound a key sequence to it (local key monitor):
case [NSEvent.ModifierFlags.option, NSEvent.ModifierFlags.command]:
guard let window = NSApp.keyWindow, let wvc = window.contentViewController as? WebViewController else {
NSSound(named: "Sosumi")?.play()
return true
}
wvc.snapshot(self)
return true
and work within a sandbox environment. We keep a bunch of user settings in the defaults like where they want snapshots to be captured (~/Desktop), so 1st time around we ask/authenticate, cache it, and the app delegate on subsequent invocations will restore sandbox bookmark(s).
var webImageView = NSImageView.init()
var player: AVAudioPlayer?
#IBAction func snapshot(_ sender: Any) {
webView.takeSnapshot(with: nil) {image, error in
if let image = image {
self.webImageView.image = image
} else {
print("Failed taking snapshot: \(error?.localizedDescription ?? "--")")
self.webImageView.image = nil
}
}
guard let image = webImageView.image else { return }
guard let tiffData = image.tiffRepresentation else { NSSound(named: "Sosumi")?.play(); return }
// 1st around authenticate and cache sandbox data if needed
if appDelegate.isSandboxed(), appDelegate.desktopData == nil {
let openPanel = NSOpenPanel()
openPanel.message = "Authorize access to Desktop"
openPanel.prompt = "Authorize"
openPanel.canChooseFiles = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = false
openPanel.directoryURL = appDelegate.getDesktopDirectory()
openPanel.begin() { (result) -> Void in
if (result == NSApplication.ModalResponse.OK) {
let desktop = openPanel.url!
_ = self.appDelegate.storeBookmark(url: desktop, options: self.appDelegate.rwOptions)
self.appDelegate.desktopData = self.appDelegate.bookmarks[desktop]
UserSettings.SnapshotsURL.value = desktop.absoluteString
}
}
}
// Form a filename: ~/"<app's name> View Shot <timestamp>"
let dateFMT = DateFormatter()
dateFMT.dateFormat = "yyyy-dd-MM"
let timeFMT = DateFormatter()
timeFMT.dateFormat = "h.mm.ss a"
let now = Date()
let path = URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value).appendingPathComponent(
String(format: "%# View Shot %# at %#.png", appDelegate.appName, dateFMT.string(from: now), timeFMT.string(from: now)))
let bitmapImageRep = NSBitmapImageRep(data: tiffData)
// With sandbox clearance to the desktop...
do
{
try bitmapImageRep?.representation(using: .png, properties: [:])?.write(to: path)
// https://developer.apple.com/library/archive/qa/qa1913/_index.html
if let asset = NSDataAsset(name:"Grab") {
do {
// Use NSDataAsset's data property to access the audio file stored in Sound.
player = try AVAudioPlayer(data:asset.data, fileTypeHint:"caf")
// Play the above sound file.
player?.play()
} catch {
Swift.print("no sound for you")
}
}
} catch let error {
NSApp.presentError(error)
NSSound(named: "Sosumi")?.play()
}
}
I am trying to capture the image that the webview is displaying to the user, so I can some color analysis of the web page. When I try to get the image from it's parent, I am basically getting a white box, even though the page has rendered:
func makeImageSnapshot()-> (NSImage)
{
let imgSize = self.view.bounds.size
let bir = self.viewbitmapImageRepForCachingDisplayInRect(self.webView!.view.bounds)
bir.size = imgSize
self.webView.cacheDisplayInRect(self.view.bounds, toBitmapImageRep:bir)
let image = NSImage(size:imgSize)
image.addRepresentation(bir)
self.image = image
return image
}
func saveSnapshot()
{
let imgRep = self.image!.representations[0]
let data = imgRep.representationUsingType(NSBitmapImageFileType.NSPNGFileType, properties: nil)
data.writeToFile("/tmp/file.png", atomically: false)
}
It looks to me like I can't get access to the properties of the actual view (in this case the bounds) inside of the webView. When I try to access it, the compiler barfs:
/Users/josh/Canary/MacOsCanary/canary/canary/Modules/Overview/Overview.swift:55:37: '(NSView!, stringForToolTip: NSToolTipTag, point: NSPoint, userData: UnsafePointer<()>) -> String!' does not have a member named 'bounds'
My guess is that this is happening due to the extensions approach used by OS X and iOS. Any ideas, or should I just go back to using the legacy WebView?
I realise the question was for Mac OS X, but I found this page whilst searching for an iOS solution. My answer below doesn't work on Mac OS X as the drawViewHierarchyInRect() API call is currently iOS only, but I put it here for reference for other iOS searchers.
This Stackoverflow answer solved it for me on iOS 8 with a WKWebView. That answer's sample code is in Objective-C but the Swift equivalent to go in a UIView sub-class or extension would be along the lines of the code below. The code ignores the return value of drawViewHierarchyInRect(), but you may want to pay attention to it.
func imageSnapshot() -> UIImage
{
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0);
self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true);
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
Swift 3
extension WKWebView {
func screenshot() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0);
self.drawHierarchy(in: self.bounds, afterScreenUpdates: true);
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return snapshotImage;
}
}
Note: This solution only works on iOS.
Found myself in the same boat today but found a solution (by using private APIs).
If you're not targeting the App Store and generally are not afraid of using private APIs, here's a way to capture screenshots of WKWebView's on OS X:
https://github.com/lemonmojo/WKWebView-Screenshot
You will need to have access to a target writeable place - the snapshotURL ie.., such as the desktop, so we provide a handler for that:
func registerSnaphotsURL(_ sender: NSMenuItem, handler: #escaping (URL) -> Void) {
var targetURL : URL
// 1st around authenticate and cache sandbox data if needed
if isSandboxed, desktopData == nil {
targetURL =
UserSettings.SnapshotsURL.value.count == 0
? getDesktopDirectory()
: URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value, isDirectory: true)
let openPanel = NSOpenPanel()
openPanel.message = "Authorize access to "
openPanel.prompt = "Authorize"
openPanel.canChooseFiles = false
openPanel.canChooseDirectories = true
openPanel.canCreateDirectories = true
openPanel.directoryURL = targetURL
openPanel.begin() { (result) -> Void in
if (result == .OK) {
targetURL = openPanel.url!
// Since we do not have data, clear any bookmark
if self.storeBookmark(url: targetURL, options: self.rwOptions) {
self.desktopData = self.bookmarks[targetURL]
UserSettings.SnapshotsURL.value = targetURL.absoluteString
if !self.saveBookmarks() {
print("Yoink, unable to save snapshot bookmark")
}
self.desktopData = self.bookmarks[targetURL]
handler(targetURL)
}
}
else
{
return
}
}
}
else
{
targetURL =
UserSettings.SnapshotsURL.value.count == 0
? getDesktopDirectory()
: URL.init(fileURLWithPath: UserSettings.SnapshotsURL.value, isDirectory: true)
handler(targetURL)
}
}
we wanted to allow single (view controller) and all current views (app delegate) so two actions in their respective files, both making use of the register handler.
App Delegate
#objc #IBAction func snapshotAllPress(_ sender: NSMenuItem) {
registerSnaphotsURL(sender) { (snapshotURL) in
// If we have a return object just call them, else notify all
if let wvc : WebViewController = sender.representedObject as? WebViewController {
sender.representedObject = snapshotURL
wvc.snapshot(sender)
}
else
{
sender.representedObject = snapshotURL
let notif = Notification(name: Notification.Name(rawValue: "SnapshotAll"), object: sender)
NotificationCenter.default.post(notif)
}
}
}
View Controller
func viewDidLoad() {
NotificationCenter.default.addObserver(
self,
selector: #selector(WebViewController.snapshotAll(_:)),
name: NSNotification.Name(rawValue: "SnapshotAll"),
object: nil)
}
#objc func snapshotAll(_ note: Notification) {
snapshot(note.object as! NSMenuItem)
}
view singleton action
#objc #IBAction func snapshotPress(_ sender: NSMenuItem) {
guard let url = webView.url, url != webView.homeURL else { return }
guard let snapshotURL = sender.representedObject as? URL else {
// Dispatch to app delegate to handle a singleton
sender.representedObject = self
appDelegate.snapshotAllPress(sender)
return
}
sender.representedObject = snapshotURL
snapshot(sender)
}
the webView interaction to capture an image
#objc func snapshot(_ sender: NSMenuItem) {
guard let url = webView.url, url != webView.homeURL else { return }
guard var snapshotURL = sender.representedObject as? URL else { return }
// URL has only destination, so add name and extension
let filename = String(format: "%# Shapshot at %#",
(url.lastPathComponent as NSString).deletingPathExtension,
String.prettyStamp())
snapshotURL.appendPathComponent(filename)
snapshotURL = snapshotURL.appendingPathExtension("png")
webView.takeSnapshot(with: nil) { image, error in
if let image = image {
self.webImageView.image = image
DispatchQueue.main.async {
self.processSnapshotImage(image, to: snapshotURL)
}
}
else
{
self.userAlertMessage("Failed taking snapshot", info: error?.localizedDescription)
self.webImageView.image = nil
}
}
}
and the capture to the targeted area
func processSnapshotImage(_ image: NSImage, to snapshotURL: URL) {
guard let tiffData = image.tiffRepresentation else { NSSound(named: "Sosumi")?.play(); return }
let bitmapImageRep = NSBitmapImageRep(data: tiffData)
do
{
try bitmapImageRep?.representation(using: .png, properties: [:])?.write(to: snapshotURL)
// https://developer.apple.com/library/archive/qa/qa1913/_index.html
if let asset = NSDataAsset(name:"Grab") {
do {
// Use NSDataAsset's data property to access the audio file stored in Sound.
let player = try AVAudioPlayer(data:asset.data, fileTypeHint:"caf")
// Play the above sound file.
player.play()
} catch {
print("no sound for you")
}
}
if snapshotURL.hideFileExtensionInPath(), let name = snapshotURL.lastPathComponent.removingPercentEncoding {
print("snapshot => \(name)")
}
} catch let error {
appDelegate.userAlertMessage("Snapshot failed", info: error.localizedDescription)
}
}