How to create a Swift method to use evaluateJavaScript with GCD - swift

How do I create a macOS method that takes a URL parameter and returns HTML using evaluateJavaScript?
I want to scrape a URL that uses JavaScript, so neither String(contentsOf: url) nor URLSession.shared.dataTask(with: url) seem to work.
The code below works, but it creates a global, rather than returning a String. I want code I can reuse in other similar projects.
Can I use didSet{} or GCD DispatchGroup() and group.enter() and group.leave() to create either a class or ViewController extension to create a simple method that returns only when the page loads and the JavaScript completes? I assume I'll need a class to catch the WKNavigationDelegate calls.
I want a macOS method, not an iOS method.
Here is my working code:
ViewController.swift
import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
var webView: WKWebView!
var webString: String = "" {
didSet {
print("webString: \(webString.count) Bytes")
print("\(webString)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.webView = WKWebView()
webView.navigationDelegate = self
let url = URL(string: "https://www.someWebsite.com")!
let myRequest = URLRequest(url: url)
webView.load(myRequest)
}
// WKNavigationDelegate methods
func webView(_ webView: WKWebView, didFinish: WKNavigation!) {
print(#function)
webView.evaluateJavaScript(
"document.documentElement.outerHTML.toString()",
completionHandler: { (html: Any?, error: Error?) in
self.webString = html as! String
print("In compHandler, HTML: \(self.webString.count) bytes\n")
})
}
func webView(_ webView: WKWebView, didFail: WKNavigation!, withError: Error) {
print(#function)
print("ERROR: Error=\(withError)")
}
I want something similar to this code that I use for synchronous dowloads:
func downloadDataTask(url: String) -> String {
var returnStr = ""
guard let myURL = URL(string: url) else {
showErrorAlert(
title: "ERROR: Invalid URL",
message: "Invalid URL in downloadDataTask()\n URL:'\(url)'")
return("Error: Invalid URL: '\(url)'")
}
let group = DispatchGroup()
group.enter()
let task = URLSession.shared.dataTask(with: myURL) { data, urlResponse, error in
if let myData = data {
let string = String(decoding: myData, as: UTF8.self)
returnStr = string
group.leave()
} else {
showErrorAlert(
title: "ERROR: Unable to load HTML from URL",
message: "Unable to load HTML in downloadDataTask()\n")
group.leave()
}
}
task.resume()
// wait for async completion handler task to complete
group.wait()
return(returnStr)
}
Any help would be appreciated!

Related

Swift unable to find "" in scope

I've encountered this issue a few times where I am trying to either pass a function from one swift file, to another, and I get the error, "Cannot find '_' in scope". I am wondering why this occurs and how to fix it? I have tried creating a public function, public func fetchJokesAPI() , along with making the class of this function an Observable object so that I am able to pass this function throughout my swift files. Why is the compiler not finding my function in the scope? Below is some code to further give reference to the issue.
//Model
import Foundation
struct DecodingError: Error{}
struct JokesModel: Codable {
let type: String
let value: Value
}
// MARK: - Value
struct Value: Codable {
let id: Int
let joke: String
}
class JokesAPI {
var session: URLSession?
// MARK: Getting random jokes from API endpoint
func fetchJokes() {
guard let url = URL(string: "https://api.icndb.com/jokes/random/") else {
print("Cannot generate URL")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print(error.localizedDescription)
return
}
guard (response as? HTTPURLResponse) != nil else {
print("No Response")
return
}
struct JokesResponse: Decodable {
let data: [JokesModel]
}
guard let data = data, let dataString = String(data: data, encoding: .utf8)
else {
print("No Data")
return
}
do {
let jokeResponse = try JSONDecoder().decode(JokesResponse.self, from: data)
print(jokeResponse.data)
} catch {
print("Error decoding joke response:", error)
}
}
task.resume()
}
}
// Another Swift File that is a Coco touch file.
import UIKit
import Foundation
class HomeViewController: UIViewController {
private let service = JokesAPI()
private var joke: [JokesModel]?
#IBOutlet var tellAeAJokeButton: UIButton!
#IBOutlet var jokeLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
service.fetchJokes()
}
#IBAction func didPressBtn() {
// MARK: Actions for both fetching a joke from the API, & Text-Speech using AI Voice
service.fetchJokes()
joke = [JokesModel]()
jokeLabel.text = JokesModel().joke
// Value of type 'JokesModel' has no member 'joke'
// Missing argument for parameter 'from' in call
You are getting the error because you need an instance of the class to call your function on.
To get your code working you can do something like this where your viewController takes a service the JokesWebService like so, the better option would be to have the service on a viewModel if you already have one but if not this should do the trick to get you started:
import UIKit
class HomeViewController: UIViewController {
#IBOutlet var tellAeAJokeButton: UIButton!
private var service: JokesWebService!
override func viewDidLoad() {
super.viewDidLoad()
service = JokesWebService()
}
#IBAction func didPressBtn() {
service.fetchJokesAPI()
}
}

How to download a blob URI using AlamoFire

I am trying to work with a WKWebView in swift and currently have a download engine using AlamoFire. I have run into a site that uses the blob: url scheme to download items. Is there a way to download blob files using AlamoFire or WKWebView in general?
My specific goal is to download the content from this blob URI to a file.
I would appreciate any help. Thank you.
All relevant code is attached below.
Here's the URL I was having a problem with:
blob:https://cubari.moe/87d49857-dfef-4f0f-bb83-db8517fd3094
Here is the error in my logs:
2021-12-10 22:41:45.382527-0500 Asobi[14529:358202] -canOpenURL: failed for URL: "blob:https://cubari.moe/87d49857-dfef-4f0f-bb83-db8517fd3094" - error: "This app is not allowed to query for scheme blob"
2021-12-10 22:41:45.474214-0500 Asobi[14529:358357] Task <4B011CC1-60E9-4AAD-98F0-BB6A6D0C92FB>.<1> finished with error [-1002] Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={NSLocalizedDescription=unsupported URL, NSErrorFailingURLStringKey=blob:https://cubari.moe/87d49857-dfef-4f0f-bb83-db8517fd3094, NSErrorFailingURLKey=blob:https://cubari.moe/87d49857-dfef-4f0f-bb83-db8517fd3094, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDownloadTask <4B011CC1-60E9-4AAD-98F0-BB6A6D0C92FB>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDownloadTask <4B011CC1-60E9-4AAD-98F0-BB6A6D0C92FB>.<1>, NSUnderlyingError=0x6000017e99b0 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}}
2021-12-10 22:41:45.476703-0500 Asobi[14529:358202] [Process] 0x124034e18 - [pageProxyID=6, webPageID=7, PID=14540] WebPageProxy::didFailProvisionalLoadForFrame: frameID=3, domain=WebKitErrorDomain, code=102
Failed provisional nav: Error Domain=WebKitErrorDomain Code=102 "Frame load interrupted" UserInfo={_WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x6000019a88c0>, NSErrorFailingURLStringKey=blob:https://cubari.moe/87d49857-dfef-4f0f-bb83-db8517fd3094, NSErrorFailingURLKey=blob:https://cubari.moe/87d49857-dfef-4f0f-bb83-db8517fd3094, NSLocalizedDescription=Frame load interrupted}
Here is the code for my download decision handler in WKNavigation decision policy
// Check if a page can be downloaded
func webView(_ webView: WKWebView,
decidePolicyFor navigationResponse: WKNavigationResponse,
decisionHandler: #escaping (WKNavigationResponsePolicy) -> Void) {
if navigationResponse.canShowMIMEType {
decisionHandler(.allow)
} else {
let url = navigationResponse.response.url
// Alternative to decisionHandler(.download) since that's iOS 15 and up
//let documentUrl = url?.appendingPathComponent(navigationResponse.response.suggestedFilename!)
parent.webModel.downloadDocumentFrom(url: url!)
decisionHandler(.cancel)
}
}
Here is the code for my download data function (it uses the AF.download method)
// Download file from page
func downloadDocumentFrom(url downloadUrl : URL) {
if currentDownload != nil {
showDuplicateDownloadAlert = true
return
}
let queue = DispatchQueue(label: "download", qos: .userInitiated)
var lastTime = Date()
let destination: DownloadRequest.Destination = { tempUrl, response in
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let suggestedName = response.suggestedFilename ?? "unknown"
let fileURL = documentsURL.appendingPathComponent(suggestedName)
return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
}
self.showDownloadProgress = true
currentDownload = AF.download(downloadUrl, to: destination)
.downloadProgress(queue: queue) { progress in
if Date().timeIntervalSince(lastTime) > 1.5 {
lastTime = Date()
DispatchQueue.main.async {
self.downloadProgress = progress.fractionCompleted
}
}
}
.response { response in
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.showDownloadProgress = false
self.downloadProgress = 0.0
}
if response.error == nil, let currentPath = response.fileURL {
self.downloadFileUrl = currentPath
self.showFileMover = true
}
if let error = response.error {
self.errorDescription = "Download could not be completed. \(error)"
self.showError = true
}
}
}
After a few days, I was able to figure out how to download a blob URL without WKDownloadDelegate. The following code builds upon this answer.
A message handler needs to be created to respond to JS messages. I created this in the makeUIView function
webModel.webView.configuration.userContentController.add(context.coordinator, name: "jsListener")
Inside your WKNavigationDelegate, you need to add this code on a navigation action.
NOTE: Since I use SwiftUI, all of my variables/models are located in the parent class (UIViewRepresentable coordinator).
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url, let scheme = url.scheme?.lowercased() {
if scheme == "blob" {
// Defer to JS handling
parent.webModel.executeBlobDownloadJS(url: url)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
Here's the JS to request for the blob stored in the browser memory. I added this JS in a wrapper function which called evaluateJavaScript with the url for cleanliness of my code.
function blobToDataURL(blob, callback) {
var reader = new FileReader()
reader.onload = function(e) {callback(e.target.result.split(",")[1])}
reader.readAsDataURL(blob)
}
async function run() {
const url = "\(url)"
const blob = await fetch(url).then(r => r.blob())
blobToDataURL(blob, datauri => {
const responseObj = {
url: url,
mimeType: blob.type,
size: blob.size,
dataString: datauri
}
window.webkit.messageHandlers.jsListener.postMessage(JSON.stringify(responseObj))
})
}
run()
In addition to the returned JS object, I had to make a struct where I can deserialize the JSON string:
struct BlobComponents: Codable {
let url: String
let mimeType: String
let size: Int64
let dataString: String
}
I then took the messages sent to the WKScriptMessageHandler and interpreted them for saving to files. I used the SwiftUI file mover here, but you can do anything you want with this content.
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let jsonString = message.body as? String else {
return
}
parent.webModel.blobDownloadWith(jsonString: jsonString)
}
In my web model (needed to import CoreServices):
func blobDownloadWith(jsonString: String) {
guard let jsonData = jsonString.data(using: .utf8) else {
print("Cannot convert blob JSON into data!")
return
}
let decoder = JSONDecoder()
do {
let file = try decoder.decode(BlobComponents.self, from: jsonData)
guard let data = Data(base64Encoded: file.dataString),
let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, file.mimeType as CFString, nil),
let ext = UTTypeCopyPreferredTagWithClass(uti.takeRetainedValue(), kUTTagClassFilenameExtension)
else {
print("Error! \(error)")
return
}
let fileName = file.url.components(separatedBy: "/").last ?? "unknown"
let path = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let url = path.appendingPathComponent("blobDownload-\(fileName).\(ext.takeRetainedValue())")
try data.write(to: url)
downloadFileUrl = url
showFileMover = true
} catch {
print("Error! \(error)")
return
}
}

URLSession cannot find 'self' in scope for errors and statusCode

My question is somewhat similar to 69959018, so I have made sure to clarify as much as I can
I'm trying to use the Steam Web API to create an app that grabs everyone on my friend list in the form of a JSON dictionary. I'm trying to use foundation instead of Alamofire in order to learn Foundation better.
So far, what I've done is the following in AppDelegate.swift:
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
var apiKey: String = "[REDACTED]"
var steamID: String = "[REDACTED]"
let getPlayerSummaries = URL(string: "http://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key=\(apiKey)&steamids=\(steamID)")
let friendList = downloadPlayerSummaries(with: getPlayerSummaries)
print(friendList)
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
}
In another file I made called networkManager.swift, I have wrote this based on what I have found in the apple documentation for "Fetching Website Data into Memory" :
//
// networkManager.swift
// Who is online?
//
// Created by Dash Interwebs on 11/21/21.
//
import Foundation
func downloadPlayerSummaries(with: URL!) {
let url = with
if url == nil {
print("url is nil")
return
}
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
if let error = error {
self.handleClientError(error)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
self.handleServerError(response)
return
}
}
}
task.resume()
}
After this however, self.handleClientError(error), and self.handleServerError(response) complain about being unable to find "self". I can't find anything about handleServerError or handleClientError. So where exactly is "self" in this context? I think that it might be URLSession but I'm not too sure.
You can refactor your code using a completion handler and using an enum that conforms to the Error protocol:
enum ApiError: Error {
case network(Error)
case genericError
case httpResponseError
}
func downloadPlayerSummaries(with url: URL?, completion: #escaping (_ success: Bool, _ error: ApiError?) -> Void) {
guard let url = url else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(false, .network(error))
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
completion(false, .httpResponseError)
return
}
// then handle your data. The completion should also include the kind of data your want to return
}
task.resume()
}
I haven't tested. Let me know if it works.

Write chunks of video to memory and stream them in AVKit

I am making a file encryption app in Swift for iOS. I want to support video files using the stock, native system video player. I currently have code that does it well, but it falls short because of one issue - if the file is too big, the app fills the phone's memory. I wanna avoid that by streaming chunks of data to AVKit (NOT AVFoundation) and play the chunks while staying in the context of that single video. How do I do that?
Note: sending raw data IS possible with AVKit, I have found code that does it very nicely (you can pass data to an init() function that gets data and it is transformed into an AVPlayerItem. The code is complicated and I don't understand much of it, the only thing that I found is that it uses a custom URL and passes it to a normal AVPlayerItem and (I think) every time data is requested, it is being sent to that custom URL. The weirdest part is that I haven't set up ANY sort of custom URL scheme which is associated with my app's project file.
Here is the code for the Data to AVPlayerItem that I was talking about:
import Foundation
import AVFoundation
fileprivate extension URL {
func withScheme(_ scheme: String) -> URL? {
var components = URLComponents(url: self, resolvingAgainstBaseURL: false)
components?.scheme = scheme
return components?.url
}
}
#objc protocol CachingPlayerItemDelegate {
/// Is called when the media file is fully downloaded.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, didFinishDownloadingData data: Data)
/// Is called every time a new portion of data is received.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, didDownloadBytesSoFar bytesDownloaded: Int, outOf bytesExpected: Int)
/// Is called after initial prebuffering is finished, means
/// we are ready to play.
#objc optional func playerItemReadyToPlay(_ playerItem: CachingPlayerItem)
/// Is called when the data being downloaded did not arrive in time to
/// continue playback.
#objc optional func playerItemPlaybackStalled(_ playerItem: CachingPlayerItem)
/// Is called on downloading error.
#objc optional func playerItem(_ playerItem: CachingPlayerItem, downloadingFailedWith error: Error)
}
open class CachingPlayerItem: AVPlayerItem {
class ResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate, URLSessionDataDelegate, URLSessionTaskDelegate {
var playingFromData = false
var mimeType: String? // is required when playing from Data
var session: URLSession?
var mediaData: Data?
var response: URLResponse?
var pendingRequests = Set<AVAssetResourceLoadingRequest>()
weak var owner: CachingPlayerItem?
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
if playingFromData {
// Nothing to load.
} else if session == nil {
// If we're playing from a url, we need to download the file.
// We start loading the file on first request only.
guard let initialUrl = owner?.url else {
fatalError("internal inconsistency")
}
startDataRequest(with: initialUrl)
}
pendingRequests.insert(loadingRequest)
processPendingRequests()
return true
}
func startDataRequest(with url: URL) {
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
session?.dataTask(with: url).resume()
}
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
pendingRequests.remove(loadingRequest)
}
// MARK: URLSession delegate
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
mediaData?.append(data)
processPendingRequests()
owner?.delegate?.playerItem?(owner!, didDownloadBytesSoFar: mediaData!.count, outOf: Int(dataTask.countOfBytesExpectedToReceive))
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
completionHandler(Foundation.URLSession.ResponseDisposition.allow)
mediaData = Data()
self.response = response
processPendingRequests()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let errorUnwrapped = error {
owner?.delegate?.playerItem?(owner!, downloadingFailedWith: errorUnwrapped)
return
}
processPendingRequests()
owner?.delegate?.playerItem?(owner!, didFinishDownloadingData: mediaData!)
}
// MARK: -
func processPendingRequests() {
// get all fullfilled requests
let requestsFulfilled = Set<AVAssetResourceLoadingRequest>(pendingRequests.compactMap {
self.fillInContentInformationRequest($0.contentInformationRequest)
if self.haveEnoughDataToFulfillRequest($0.dataRequest!) {
$0.finishLoading()
return $0
}
return nil
})
// remove fulfilled requests from pending requests
_ = requestsFulfilled.map { self.pendingRequests.remove($0) }
}
func fillInContentInformationRequest(_ contentInformationRequest: AVAssetResourceLoadingContentInformationRequest?) {
// if we play from Data we make no url requests, therefore we have no responses, so we need to fill in contentInformationRequest manually
if playingFromData {
contentInformationRequest?.contentType = self.mimeType
contentInformationRequest?.contentLength = Int64(mediaData!.count)
contentInformationRequest?.isByteRangeAccessSupported = true
return
}
guard let responseUnwrapped = response else {
// have no response from the server yet
return
}
contentInformationRequest?.contentType = responseUnwrapped.mimeType
contentInformationRequest?.contentLength = responseUnwrapped.expectedContentLength
contentInformationRequest?.isByteRangeAccessSupported = true
}
func haveEnoughDataToFulfillRequest(_ dataRequest: AVAssetResourceLoadingDataRequest) -> Bool {
let requestedOffset = Int(dataRequest.requestedOffset)
let requestedLength = dataRequest.requestedLength
let currentOffset = Int(dataRequest.currentOffset)
guard let songDataUnwrapped = mediaData,
songDataUnwrapped.count > currentOffset else {
// Don't have any data at all for this request.
return false
}
let bytesToRespond = min(songDataUnwrapped.count - currentOffset, requestedLength)
let dataToRespond = songDataUnwrapped.subdata(in: Range(uncheckedBounds: (currentOffset, currentOffset + bytesToRespond)))
dataRequest.respond(with: dataToRespond)
return songDataUnwrapped.count >= requestedLength + requestedOffset
}
deinit {
session?.invalidateAndCancel()
}
}
fileprivate let resourceLoaderDelegate = ResourceLoaderDelegate()
fileprivate let url: URL
fileprivate let initialScheme: String?
fileprivate var customFileExtension: String?
weak var delegate: CachingPlayerItemDelegate?
open func download() {
if resourceLoaderDelegate.session == nil {
resourceLoaderDelegate.startDataRequest(with: url)
}
}
private let cachingPlayerItemScheme = "cachingPlayerItemScheme"
/// Is used for playing remote files.
convenience init(url: URL) {
self.init(url: url, customFileExtension: nil)
}
/// Override/append custom file extension to URL path.
/// This is required for the player to work correctly with the intended file type.
init(url: URL, customFileExtension: String?) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let scheme = components.scheme,
var urlWithCustomScheme = url.withScheme(cachingPlayerItemScheme) else {
fatalError("Urls without a scheme are not supported")
}
self.url = url
self.initialScheme = scheme
if let ext = customFileExtension {
urlWithCustomScheme.deletePathExtension()
urlWithCustomScheme.appendPathExtension(ext)
self.customFileExtension = ext
}
let asset = AVURLAsset(url: urlWithCustomScheme)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
super.init(asset: asset, automaticallyLoadedAssetKeys: nil)
resourceLoaderDelegate.owner = self
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)
}
/// Is used for playing from Data.
init(data: Data, mimeType: String, fileExtension: String) {
guard let fakeUrl = URL(string: cachingPlayerItemScheme + "://whatever/file.\(fileExtension)") else {
fatalError("internal inconsistency")
}
self.url = fakeUrl
self.initialScheme = nil
resourceLoaderDelegate.mediaData = data
resourceLoaderDelegate.playingFromData = true
resourceLoaderDelegate.mimeType = mimeType
let asset = AVURLAsset(url: fakeUrl)
asset.resourceLoader.setDelegate(resourceLoaderDelegate, queue: DispatchQueue.main)
super.init(asset: asset, automaticallyLoadedAssetKeys: nil)
resourceLoaderDelegate.owner = self
addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil)
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledHandler), name:NSNotification.Name.AVPlayerItemPlaybackStalled, object: self)
}
// MARK: KVO
override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
delegate?.playerItemReadyToPlay?(self)
}
// MARK: Notification hanlers
#objc func playbackStalledHandler() {
delegate?.playerItemPlaybackStalled?(self)
}
// MARK: -
override init(asset: AVAsset, automaticallyLoadedAssetKeys: [String]?) {
fatalError("not implemented")
}
deinit {
NotificationCenter.default.removeObserver(self)
removeObserver(self, forKeyPath: "status")
resourceLoaderDelegate.session?.invalidateAndCancel()
}
}
I put it here in case anybody wants to use it. I didn't write this, this works was made by GitHub user "neekeetab". Here's the file.
If anyone can answer me about how to send AVKit a stream of data chunks that are inside of the iDevice's memory, I will be happy to hear from you. Even if you don't, thanks for stopping by!

Never ending loop while loading HTML in Web View

I try to load webView with existing website.
I added buttons on a navigation bar which have url links:
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
var myHTMLString = ""
guard let myURL = URL(string: domainURLString) else {
print("Error: \(domainURLString) doesn't seem to be a valid URL")
return
}
do {
myHTMLString = try String(contentsOf: myURL, encoding: .utf8)
} catch let error {
print("Error: \(error)")
}
webView.frame = view.bounds
webView.scalesPageToFit = true
createBottomNavigationBar()
webView.loadHTMLString(myHTMLString as String, baseURL: nil)
}
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool {
print("===============================start===============================")
let myURLString:String = (request.url?.absoluteString)!
var myHTMLString = ""
if navigationType == UIWebViewNavigationType.linkClicked {
print("link clicked")
webView.stopLoading()
var myHTMLString = ""
let myURL = URL(string: myURLString)
do {
myHTMLString = try String(contentsOf: myURL!, encoding: .utf8)
} catch let error {
print("Error: \(error)")
return true
}
webView.loadHTMLString(myHTMLString as String, baseURL: nil)
print("first")
print(request.url)
}else if(navigationType == UIWebViewNavigationType.other){
print("This is for Navigation Button click")
//webView.loadHTMLString(myHTMLString as String, baseURL: nil)
}
else{
print("else")
}
return true
However, When I uncomment
webView.loadHTMLString(myHTMLString as String, baseURL: nil)
in this statement:
}else if(navigationType == UIWebViewNavigationType.other){
the app loops forever.
The statement is for when one of the navigation buttons click fires. It redirects to internal url like "http://yahoo.com/mail".
That would be the expected behaviour based on your code. The line
webView.loadHTMLString(myHTMLString as String, baseURL: nil)
in viewDidLoad will try to load the HTML. This in turn will call the delegate method
webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebViewNavigationType) -> Bool
Inside the delegate method, when your code hits the else if case, it will again call
webView.loadHTMLString(myHTMLString as String, baseURL: nil)
which in turn will call the delegate method again. It's a cyclical loop. The code is working as it should. You need to change the logic.