So I have a dynamic link that is working in that it opens up the app when I click on it but the handling of the dynamic link doesn't happen. This is because application function seen below is never entered and I'm not sure why...
func handleIncomingDynamicLink(_ dynamicLink: DynamicLink){
guard let url = dynamicLink.url else {
print("That's weird. My dynamic link object has no url")
return
}
print("Your incoming link parameter is \(url.absoluteString)")
}
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
if let incomingURL = userActivity.webpageURL {
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error! \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handleIncomingDynamicLink(dynamicLink)
}
}
if linkHandled {
return true
} else {
return false
}
}
return false
}
Any help would be greatly appreciated--is it possible this is a problem with Firebase itself or no?
SwiftUI 2.0 / #main App
For the new SwiftUI 2.0 you can make an app only using SwiftUI. This means you don't use AppDelegate or SceneDelegate. You then have to use .onOpenURL to get any action called when you click on your link. I am usling this for Firebase Dynamic Links.
WindowGroup {
ContentView()
.onOpenURL { url in
// handle the URL that must be opened
let incomingURL = url
print("Incoming URL is: \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else {
print("Error found!: \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handleDynamicLink(dynamicLink)
}
}
if linkHandled{
print("Link Handled")
return
}else{
print("NO linkHandled")
return
}
}
}
Configuring Firebase Dynamic Links on iOS has several steps that must be followed. So, I recommend referring to the official documentation here. Additionally, there is a Firecasts series that walks through all of the steps here.
I followed the Firecasts series, but my setup is slightly different; so, I ran into a couple of issues. First, I am using a custom domain. Second, I'm using SwiftUI. I'll just address the issues that I had, so please refer to the full Firecasts series for all of the gruesome details.
SwiftUI 2.0
There are two different types of links that your app needs to handle. First, your app needs to handle links when your app is already installed (i.e. https://app.yourcustomdomain.com/...). Second, your app needs to handle links copied into the clipboard before your app was installed (i.e. com.your.bundle.id://google/link/...).
Also, you probably want to handle links originating from a web browser (Safari). In my case, I process a link from a QR code scan. Apparently NFC tag scan links also originate from Safari, but I can't confirm this.
After some trial and error, I found a way to handle all of these concerns with SwiftUI; it's unclear if this is the officially recommended approach, so proceed with caution.
First, use onOpenURL(perform:) and .onContinueUserActivity(_,perform:):
var body: some Scene {
WindowGroup {
ContentView()
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb, perform: onWebBrowserActivity)
.onOpenURL(perform: onOpenURL)
}
}
Second, define the onContinueUserActivity handler:
// Handles links originating from the web browser
func onWebBrowserActivity(_ userActivity: NSUserActivity) {
guard let url = userActivity.webpageURL else {
return
}
onOpenURL(url)
}
Third, define the onOpenURL handler:
func onOpenURL(_ url: URL) {
let dynamicLinks = DynamicLinks.dynamicLinks()
if dynamicLinks.shouldHandleDynamicLink(fromCustomSchemeURL: url),
let dynamicLink = dynamicLinks.dynamicLink(fromCustomSchemeURL: url) {
handle(dynamicLink: dynamicLink)
} else {
handleUniversalLink(url: url)
}
}
Both types of links mentioned above are passed into the onOpenURL handler, so this if statement handles the two cases.
Finally, (as others have mentioned) handle universal links:
func handleUniversalLink(url: URL) {
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(url) { dynamicLink, error in
if let error = error {
print("Could not follow the dynamic link \(error.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handle(dynamicLink: dynamicLink)
}
}
if linkHandled {
print("Link handled")
} else {
print("Link not handled")
}
}
Custom Domain
I had to follow the custom domain setup instructions here. Specifically, I had to set up Firebase Hosting, which requires adding an A DNS record (In addition to setting up a TXT DNS record for the Dynamic Links URL Prefix setup). Also, I had to specify the custom domain in my Info.plist
<key>FirebaseDynamicLinksCustomDomains</key>
<array>
<string>https://app.yourcustomdomain.com</string>
</array>
SwiftUI 1.0 / SceneDelegate
You do the same but in your SceneDelegate, following callback:
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
if let incomingURL = userActivity.webpageURL {
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error! \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handleIncomingDynamicLink(dynamicLink)
}
}
}
SwiftUI 2.0 / WindowGroup
With SwiftUI 2.0 life-cycle you need to use instead .onContinueUserActivity, like
WindowGroup {
ContentView()
.onContinueUserActivity(<your_activity_type_here>) { userActivity in
if let incomingURL = userActivity.webpageURL {
print("Incoming URL is \(incomingURL)")
let linkHandled = DynamicLinks.dynamicLinks().handleUniversalLink(incomingURL) { (dynamicLink, error) in
guard error == nil else{
print("Found an error! \(error!.localizedDescription)")
return
}
if let dynamicLink = dynamicLink {
self.handleIncomingDynamicLink(dynamicLink)
}
}
}
}
}
Related
I am making new app with Xcode using Swift and i am fetching posts from my WordPress website , all works fine but there is one problem, when i scroll down to the very last post of category then the indicator is just running and nothing happens, i want when there is no more post then Progress bar should stop running and i want to toast a message that there is no more post , how is that possible ? this is my code to fetch posts
func fetchPostData(completionHandler: #escaping ([Postimage]) -> Void ) {
let url = URL(string: "https://www.sikhnama.com/wp-json/wp/v2/posts/?categories=4&page=\(page)\(sortBy)")!
print(url)
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
do {
let postsData = try JSONDecoder().decode([Postimage].self, from: data)
completionHandler(postsData)
DispatchQueue.main.async {
if(self.newsData.isEmpty == false){
print("collection view empty")
self.collectionView.reloadData()
SVProgressHUD.dismiss()
}
else{
if(self.collectionView == nil){
print("collection view nill")
self.fetchPostData { (posts) in
self.newsData = posts }
}
}
}
}
catch {
let error = error
print(String(describing: error))
}
}
task.resume()
}
can you please help ?
I am having a lot of trouble downloading pictures from URL for my tableview cells. I tried both synchronously and asynchronously downloading, but none of them worked.
For synchronous download, Xcode gives me purple warnings and the pictures won't show up in my tableview.
Warning:
"Synchronous URL loading of ... should not occur on this application's main thread as it may lead to UI unresponsiveness. Please switch to an asynchronous networking API such as URLSession."
For asynchronous download, the code after the downloading executes right away, and the download is not able to finish and results in a nil.
What should I do?
My code:
(I am loading tableview in batches of 15 posts, this is the code of loading batches)
func reloadBatch(){
for i in currentRow...currentRow+15{
if i == posts.count{
return
}
let post = posts[i] // documens
if post.posttype == 1{
let uP = UIImage(url: URL(string: post.userphoto!)) ?? UIImage(named: "Jordan")
postCell.append(LoadedCellModel(posttype: 1, sender: post.sender, userphoto: uP, title: post.title, photo: nil, videoURL: nil, content: post.content))
}else if post.posttype == 2{
let uP = UIImage(url: URL(string: post.userphoto!)) ?? UIImage(named: "Jordan")
let pic = UIImage(url: URL(string: post.photo![0])) ?? UIImage(named: "Jordan")
// This is the picture that does not show up, "photo" is an array of pictures' URL(in string)
postCell.append(LoadedCellModel(posttype: 2, sender: post.sender, userphoto: uP, title: post.title, photo: pic, videoURL: nil, content: post.content))
print(pic)
}
}
currentRow += 15
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
extension UIImage {
convenience init?(url: URL?) {
guard let url = url else { return nil }
do {
self.init(data: try Data(contentsOf: url))
} catch {
print("Cannot load image from url: \(url) with error: \(error)")
return nil
}
}
}
Getting data such as an image from the net is asynchronous.
That is, it takes time to do. So you need to "wait" until the data is available
before you can use it. Note the warning message ...Please switch to an asynchronous networking API such as URLSession
There are many ways to do this. Here I present a simple
way, using a function with a completion handler.
This means, you cannot use this function (getImage(url: ..)) like you are trying to do in:
let uP = UIImage(url: URL(string: post.userphoto!)) ?? UIImage(named: "Jordan")
You have to use the closure:
getImage(url: url) { img in
// do something with img
}
Here is an example code for downloading one image:
struct ContentView: View {
#State var uimg: UIImage?
let token = "xxxx" // <-- your secret token
var body: some View {
VStack {
if uimg == nil {
Text("downloading")
ProgressView()
} else {
Image(uiImage: uimg!).resizable().frame(width: 333, height: 333)
}
}
.onAppear {
guard let url = URL(string: "https://firebasestorage.googleapis.com/v0/b/pirateforum-f2f04.appspot.com/o/images%2F1663116366.2403781.png?alt=media&token=\(token)") else { return }
getImage(url: url) { img in
self.uimg = img
}
}
}
func getImage(url: URL, completion: #escaping (UIImage?) -> Void) {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data, let img = UIImage(data: data) {
completion(img)
} else {
completion(nil)
}
}.resume()
}
}
P.S. do not show your secret token in the warning message, remove it.
Here is the code I currently have however, it does not seem to be working. This example says I want to open the calc app. My goal is to open an app once a widget is clicked.
#main App Code:
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
print("Received deep link: \(url)")
}
}
}
Widget Code:
Gauge(value: 50), in: 0.0...100.0) {
} currentValueLabel: {
Text(Open App)
}
.gaugeStyle(.accessoryCircularCapacity)
.widgetURL(URL(string: "calc://")!)
Then you need to do this in a 2 step process. First, you need to set up your app to receive custom URLs from your widget. This is shockingly well explained by Apple here. Once you have your app's custom url scheme set up, it is time to set up your widget. Essentially what you are going to do is send a URL with a query that is the URL you want to open. Back in your app, you receive that URL, parse it out, and then call openUrl() with the URL you want to open, and that app will open.
Your code above is close. Following Apple's example above, try this:
In your widget create a deep link URL:
func createDeeplinkForCalc() -> URL {
var components = URLComponents()
components.scheme = "myphotoapp"
components.host = "com.example.myphotoapp"
components.path = "/calc"
components.queryItems = [
URLQueryItem(name: "open", value: "calc://")
]
return components.url!
}
Then, in .widgetURL, pass this:
.widgetURL(createDeeplinkForCalc())
In your main app:
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
handleURL(url: URL)
}
}
}
func handleURL(_ url:URL) {
// This makes sure you got the correct URL
guard url.scheme == "myphotoapp",
url.host == "com.example.myphotoapp"
else { return }
let query = parseQuery(url: url)
guard let urlString = query["open"],
!urlString.isEmpty else { return } // make sure something exists in the value
if let calcURL = URL(string: urlString) {
openURL(calcURL) // this calls "calc://" and opens the calculator
}
private func parseQuery(url: URL) -> Query {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let queryItems = components.queryItems
else { return ["":""] }
return queryItems.reduce(into: Query()) { (result, item) in
result[item.name] = item.value
}
}
}
The above has not been tested, but should work.
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 am trying to (1) download a piece of audio from a link, (2) add that newly-downloaded audio to an AVPlayer and (3) play it. Something is going wrong at step (3) and I'm looking for any guidance. Here's the code, including my alamofire and download functions, as I fear something may be going wrong at that stage.
import AVFoundation
class SettingAlarmViewController: UIViewController {
var player:AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
}
catch {
// report for an error
}
}
func getLatestPodcastURL(completion: #escaping (URL) -> ()) {
let RSSUrl: String = "https://www.npr.org/rss/podcast.php?id=510318"
Alamofire.request(RSSUrl).responseRSS() {response in
if let podcastURL: String = response.result.value?.items[0].enclosure!
{
let audioURL = URL(string: podcastURL)
completion(audioURL!)
}
else {
//error handling
}
}
}
func downloadSongAsynch(audioUrl: URL, completion: #escaping (URL) -> ()) {
let fileManager = FileManager.default
let documentsDirectoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("podcasts/")
do {
try fileManager.createDirectory(atPath: documentsDirectoryURL.path,
withIntermediateDirectories: true, attributes: nil)
} catch {
//error handling
}
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
try FileManager.default.moveItem(at: location, to: destinationUrl)
} catch {
//error handling
}
}).resume()
completion(destinationUrl)
}
#IBAction func SetUpBotton(_ sender: Any) {
getLatestPodcastURL() {response in
//Uses an asynchronous call to Alamofire to get the podcast URL
self.downloadPodcastAsynch(audioUrl: response){response2 in
self.player = AVPlayer(url: response2)
print(self.player.currentItem)
}
#IBAction func PlayButton(_ sender: Any) {
player.play()
print(player.currentItem)
}
The log consistently shows my current item: >
But nothing plays. I have checked that the audio is working by trying to use the URL to stream this content. That works fine. I am getting the following:
BoringSSL errors "[BoringSSL] Function boringssl_session_errorlog: line 2871 [boringssl_session_read] SSL_ERROR_ZERO_RETURN(6): operation failed because the connection was cleanly shut down with a close_notify alert
but from what I've read, this is just a bug in the latest update and shouldn't be impacting the download. Any thoughts on this?