Function from another class ios swift not executed - ios

Please tell me, I have 2 classes. There is a load_shop() function that enables the display of the webView.
If I call a function from the first class ViewController then the function is executed and the webView is displayed, and if from another class CheckUpdate the function is executed (print is output) but the webView is not displayed. What could be the problem?
Thanks for any help. I'm new to this.
class ViewController: UIViewController {
...
override func viewDidLoad() {
super.viewDidLoad()
self.load_shop()
}
...
func load_shop() {
view = webView
print("finish_update")
}
}
class CheckUpdate: NSObject {
if (...) {
ViewController().load_shop()
}
}
ViewController.swift
import UIKit
import WebKit
import UserNotifications
import Foundation
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.scrollView.bounces = false;
let myURL = URL(string:"https://google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest);
webView.navigationDelegate = self
}
var update = 0
func load_shop() {
view = webView
print("finish_update")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
CheckUpdate.shared.showUpdate(withConfirmation: false)
}
func close() {
exit(0);
}
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
}
CheckUpdate.swift
import Foundation
import UIKit
enum VersionError: Error {
case invalidBundleInfo, invalidResponse
}
class LookupResult: Decodable {
var results: [AppInfo]
}
class AppInfo: Decodable {
var version: String
var trackViewUrl: String
}
class CheckUpdate: NSObject {
let first_class = ViewController()
static let shared = CheckUpdate()
func showUpdate(withConfirmation: Bool) {
DispatchQueue.global().async {
self.checkVersion(force : !withConfirmation)
}
}
private func checkVersion(force: Bool) {
if let currentVersion = self.getBundle(key: "CFBundleShortVersionString") {
_ = getAppInfo { (info, error) in
if let appStoreAppVersion = info?.version {
if let error = error {
print("error getting app store version: ", error)
} else if appStoreAppVersion == currentVersion {
self.first_class.load_shop()
print("Already on the last app version: ",currentVersion)
} else {
print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
DispatchQueue.main.async {
let topController: UIViewController = (UIApplication.shared.windows.first?.rootViewController)!
topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
}
}
}
}
}
}
private func getAppInfo(completion: #escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
// You should pay attention on the country that your app is located, in my case I put Brazil */br/*
// Você deve prestar atenção em que país o app está disponível, no meu caso eu coloquei Brasil */br/*
guard let identifier = self.getBundle(key: "CFBundleIdentifier"),
let url = URL(string: "http://itunes.apple.com/br/lookup?bundleId=\(identifier)") else {
DispatchQueue.main.async {
completion(nil, VersionError.invalidBundleInfo)
}
return nil
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else { throw VersionError.invalidResponse }
let result = try JSONDecoder().decode(LookupResult.self, from: data)
print(result.results)
guard let info = result.results.first else {
throw VersionError.invalidResponse
}
completion(info, nil)
} catch {
completion(nil, error)
}
}
task.resume()
return task
}
func getBundle(key: String) -> String? {
guard let filePath = Bundle.main.path(forResource: "Info", ofType: "plist") else {
fatalError("Couldn't find file 'Info.plist'.")
}
// 2 - Add the file to a dictionary
let plist = NSDictionary(contentsOfFile: filePath)
// Check if the variable on plist exists
guard let value = plist?.object(forKey: key) as? String else {
fatalError("Couldn't find key '\(key)' in 'Info.plist'.")
}
return value
}
}
extension UIViewController {
#objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
guard let appName = CheckUpdate.shared.getBundle(key: "CFBundleName") else { return } //Bundle.appName()
let alertTitle = "New version"
let alertMessage = "A new version of \(appName) are available on AppStore. Update now!"
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
if !Force {
let notNowButton = UIAlertAction(title: "Not now", style: .default)
alertController.addAction(notNowButton)
}
let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
guard let url = URL(string: AppURL) else {
return
}
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
alertController.addAction(updateButton)
self.present(alertController, animated: true, completion: nil)
}
}

let first_class = ViewController() // stop doing this. It will not work. Think of it; you have a car (view controller), you want to turn on the radio of your car. What would you have to do? First turn your car on and then its radio right? But what you do in your code is; instead of turning on the radio of your car, you go and get a new car (like new instance of viewcontroller) and turn on the radio of the new car. But actually you need to turn on the radio of your own car not the new car. This how works instances in object oriented programming. You created an instance of a UIViewController (it is your car), then in another class you are creating another instance of UIViewController (that is not your car in the example). Then you try to modify the first one but actually you are modifying the another one. As soon as I am home I will modify your code from my laptop, because it is hard to edit it from the mobile phone.
Here is the modified code. NOTE THAT I just correct the parts where you want to call load_shop function from the CheckUpdate class. I mean I don't know all the logic behind your code so, if it contains more logical errors it's all up to you.
ViewController.swift
import UIKit
import WebKit
import UserNotifications
import Foundation // Not really necesary cause UIKit import already imports it
class ViewController: UIViewController, WKUIDelegate, WKScriptMessageHandler, WKNavigationDelegate {
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.scrollView.bounces = false;
let myURL = URL(string:"https://google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest);
webView.navigationDelegate = self
}
var update = 0
func load_shop() {
view = webView
print("finish_update")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
CheckUpdate.shared.showUpdate(withConfirmation: false, caller: self) // Attention here!!!
}
func close() {
exit(0);
}
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
}
CheckUpdate.swift
import UIKit
enum VersionError: Error {
case invalidBundleInfo, invalidResponse
}
class LookupResult: Decodable {
var results: [AppInfo]
}
class AppInfo: Decodable {
var version: String
var trackViewUrl: String
}
class CheckUpdate: NSObject {
let first_class: ViewController? // Attention here!!!
static let shared = CheckUpdate()
func showUpdate(withConfirmation: Bool, viewController: ViewController) {
first_class = viewController // Attention here!!!
DispatchQueue.global().async {
self.checkVersion(force : !withConfirmation)
}
}
private func checkVersion(force: Bool) {
if let currentVersion = self.getBundle(key: "CFBundleShortVersionString") {
_ = getAppInfo { (info, error) in
if let appStoreAppVersion = info?.version {
if let error = error {
print("error getting app store version: ", error)
} else if appStoreAppVersion == currentVersion {
self.first_class?.load_shop() // Atention here!!!
print("Already on the last app version: ",currentVersion)
} else {
print("Needs update: AppStore Version: \(appStoreAppVersion) > Current version: ",currentVersion)
DispatchQueue.main.async {
let topController: UIViewController = (UIApplication.shared.windows.first?.rootViewController)!
topController.showAppUpdateAlert(Version: (info?.version)!, Force: force, AppURL: (info?.trackViewUrl)!)
}
}
}
}
}
}
private func getAppInfo(completion: #escaping (AppInfo?, Error?) -> Void) -> URLSessionDataTask? {
// You should pay attention on the country that your app is located, in my case I put Brazil */br/*
// Você deve prestar atenção em que país o app está disponível, no meu caso eu coloquei Brasil */br/*
guard let identifier = self.getBundle(key: "CFBundleIdentifier"),
let url = URL(string: "http://itunes.apple.com/br/lookup?bundleId=\(identifier)") else {
DispatchQueue.main.async {
completion(nil, VersionError.invalidBundleInfo)
}
return nil
}
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let error = error { throw error }
guard let data = data else { throw VersionError.invalidResponse }
let result = try JSONDecoder().decode(LookupResult.self, from: data)
print(result.results)
guard let info = result.results.first else {
throw VersionError.invalidResponse
}
completion(info, nil)
} catch {
completion(nil, error)
}
}
task.resume()
return task
}
func getBundle(key: String) -> String? {
guard let filePath = Bundle.main.path(forResource: "Info", ofType: "plist") else {
fatalError("Couldn't find file 'Info.plist'.")
}
// 2 - Add the file to a dictionary
let plist = NSDictionary(contentsOfFile: filePath)
// Check if the variable on plist exists
guard let value = plist?.object(forKey: key) as? String else {
fatalError("Couldn't find key '\(key)' in 'Info.plist'.")
}
return value
}
}
extension UIViewController {
#objc fileprivate func showAppUpdateAlert( Version : String, Force: Bool, AppURL: String) {
guard let appName = CheckUpdate.shared.getBundle(key: "CFBundleName") else { return } //Bundle.appName()
let alertTitle = "New version"
let alertMessage = "A new version of \(appName) are available on AppStore. Update now!"
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
if !Force {
let notNowButton = UIAlertAction(title: "Not now", style: .default)
alertController.addAction(notNowButton)
}
let updateButton = UIAlertAction(title: "Update", style: .default) { (action:UIAlertAction) in
guard let url = URL(string: AppURL) else {
return
}
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(url)
}
}
alertController.addAction(updateButton)
self.present(alertController, animated: true, completion: nil)
}
}

If your CheckUpdate class resides in a file other than ViewController's file this is not to correct way to do it. You can define a closure or protocol in ViewController then implement it in CheckUpdate class. Something like:
class ViewController: UIViewController {
...
typeAlias OnUpdate = () -> Void
...
override func viewDidLoad() {
super.viewDidLoad()
self.load_shop()
}
...
let checkupdate = CheckUpdate(){
view = webView
print("finish_update")
}
}
class CheckUpdate: NSObject {
...
var onupdate:OnUpdate?
...
init(_ onupdate: OnUpdate?){
self.onupdate = onupdate
}
...
if (...) {
//ViewController().load_shop()
onupdate?()
}
}

Related

QLPreviewController is not working for xls,docs,txt,csv file formats iOS Swift

Using QLPreviewController for opening the multiple type document.It's only working in PDF case not other formats.I'm using this condition mimeType.hasPrefix("docx") and other file formats and execute the further code otherwise will go to else block.Code only working in pdf case other condition going to else block.Aslo I want when user click on file document immediately downloading start and save in phone.
class QuickLookViewController: UIViewController, QLPreviewControllerDelegate, QLPreviewControllerDataSource {
let previewController = QLPreviewController()
var previewItems: [PreviewItem] = []
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func openDoc(_ sender: UIButton) {
quickLook(url: URL(string: "https://filesamples.com/samples/document/txt/sample2.txt")!)
}
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
previewItems.count
}
func quickLook(url: URL) {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else {
self.presentAlertController(with: error?.localizedDescription ?? "Failed to download the documents!!!")
return
}
guard
let httpURLResponse = response as? HTTPURLResponse,
let mimeType = httpURLResponse.mimeType,
mimeType.hasPrefix("docx") || mimeType.hasPrefix("jpeg") || mimeType.hasPrefix("jpg") ||
mimeType.hasSuffix("pdf") || mimeType.hasPrefix("xls") || mimeType.hasPrefix("txt")
else {
print((response as? HTTPURLResponse)?.mimeType ?? "")
self.presentAlertController(with: "the data downloaded it is not a valid pdf file")
return
}
do {
let suggestedFilename = httpURLResponse.suggestedFilename ?? "quicklook.pdf"
var previewURL = FileManager.default.temporaryDirectory.appendingPathComponent(suggestedFilename)
try data.write(to: previewURL, options: .atomic)
previewURL.hasHiddenExtension = true
let previewItem = PreviewItem()
previewItem.previewItemURL = previewURL
self.previewItems.append(previewItem)
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
self.previewController.delegate = self
self.previewController.dataSource = self
self.previewController.currentPreviewItemIndex = 0
self.present(self.previewController, animated: true)
}
} catch {
print(error)
return
}
}.resume()
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { previewItems[index]
}
}
func presentAlertController(with message: String) {
DispatchQueue.main.async {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
let alert = UIAlertController(title: "Alert", message: message, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default))
self.present(alert, animated: true)
}
}
}
extension URL {
var hasHiddenExtension: Bool {
get { (try? resourceValues(forKeys: [.hasHiddenExtensionKey]))?.hasHiddenExtension == true }
set {
var resourceValues = URLResourceValues()
resourceValues.hasHiddenExtension = newValue
try? setResourceValues(resourceValues)
}
}
}
class PreviewItem: NSObject, QLPreviewItem {
var previewItemURL: URL?
}
A MIME type is not a file extension. For example for a Word file you should get application/vnd.openxmlformats-officedocument.wordprocessingml.document as the MIME type. Use the UniformTypeIdentifiers framework to convert to/from MIME types.

How to add close or back button in Xcode using swift language when a file such as pdf, ppt .. etc. is opened?

First of all I'm new in swift and objective c languages. My problem is that I have a WebView app for iOS device using swift language , the app when opens a document file such as pdf, ppt,...etc. there is no option to close the file or back to previous. Just googled the problem and found the solution with objective c in the link below, but my problem is that I'm using swift not objective c.
xCode add close/done button when pdf file is opened
and my code is:
import UIKit
import WebKit
import QuickLook
import AVFoundation
class ViewController: UIViewController, WKUIDelegate, WKNavigationDelegate, QLPreviewControllerDataSource, WKScriptMessageHandler {
var documentPreviewController = QLPreviewController()
var documentUrl = URL(fileURLWithPath: "")
let webViewConfiguration = WKWebViewConfiguration()
let userContentController = WKUserContentController()
var webViewCookieStore: WKHTTPCookieStore!
var webView: WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
// initial configuration of custom JavaScripts
webViewConfiguration.userContentController = userContentController
webViewConfiguration.websiteDataStore = WKWebsiteDataStore.default()
// init this view controller to receive JavaScript callbacks
userContentController.add(self, name: "openDocument")
userContentController.add(self, name: "jsError")
// QuickLook document preview
documentPreviewController.dataSource = self
// link the appDelegate to be able to receive the deviceToken
//------------
// Add script message handlers that, when run, will make the function
// window.webkit.messageHandlers.test.postMessage() available in all frames.
// controller.add(self, name: "test")
guard let scriptPath = Bundle.main.path(forResource: "script", ofType: "js"),
let scriptSource1 = try? String(contentsOfFile: scriptPath) else { return }
let userScript = WKUserScript(source: scriptSource1, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
userContentController.addUserScript(userScript)
//----- end
webView = WKWebView(frame: CGRect.zero, configuration: webViewConfiguration)
let myURL = URL(string:"My URL")
let myRequest = URLRequest(url: myURL!)
// see "The 2 delegates": // https://samwize.com/2016/06/08/complete-guide-to-implementing-wkwebview/
webView.uiDelegate = self
webView.navigationDelegate = self
view.addSubview(webView)
let layoutGuide = view.safeAreaLayoutGuide
webView.translatesAutoresizingMaskIntoConstraints = false
webView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
webView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
webView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true
self.load(myRequest)
webViewCookieStore = webView.configuration.websiteDataStore.httpCookieStore
webView.allowsBackForwardNavigationGestures = true
if(webView.canGoBack) {
//Go back in webview history
webView.goBack()
} else {
//Pop view controller to preview view controller
self.navigationController?.popViewController(animated: true)
}
let cameraMediaType = AVMediaType.video
let cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: cameraMediaType)
switch cameraAuthorizationStatus {
case .denied: break
case .authorized: break
case .restricted: break
case .notDetermined:
// Prompting user for the permission to use the camera.
AVCaptureDevice.requestAccess(for: cameraMediaType) { granted in
if granted {
print("Granted access to \(cameraMediaType)")
} else {
print("Denied access to \(cameraMediaType)")
}
}
#unknown default:
fatalError()
}
}
private func load(_ url: URL) {
load(URLRequest(url:url))
}
private func load(_ req: URLRequest) {
let request = req
// request.setValue(self.deviceToken, forHTTPHeaderField: "iosDeviceToken")
//request.setValue(self.myVersion as? String, forHTTPHeaderField: "iosVersion")
//request.setValue(self.myBuild as? String, forHTTPHeaderField: "iosBuild")
//request.setValue(UIDevice.current.modelName, forHTTPHeaderField: "iosModelName")
//debugPrintHeaderFields(of: request, withMessage: "Loading request")
webView.load(request)
debugPrint("Loaded request=\(request.url?.absoluteString ?? "n/a")")
}
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
let url = navigationAction.request.url
if openInDocumentPreview(url!) {
decisionHandler(.cancel)
executeDocumentDownloadScript(forAbsoluteUrl: url!.absoluteString)
} else {
decisionHandler(.allow)
}
}
/*
Handler method for JavaScript calls.
Receive JavaScript message with downloaded document
*/
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
debugPrint("did receive message \(message.name)")
if (message.name == "openDocument") {
previewDocument(messageBody: message.body as! String)
} else if (message.name == "jsError") {
debugPrint(message.body as! String)
}
}
/*
Open downloaded document in QuickLook preview
*/
private func previewDocument(messageBody: String) {
// messageBody is in the format ;data:;base64,
// split on the first ";", to reveal the filename
let filenameSplits = messageBody.split(separator: ";", maxSplits: 1, omittingEmptySubsequences: false)
let filename = String(filenameSplits[0])
// split the remaining part on the first ",", to reveal the base64 data
let dataSplits = filenameSplits[1].split(separator: ",", maxSplits: 1, omittingEmptySubsequences: false)
let data = Data(base64Encoded: String(dataSplits[1]))
if (data == nil) {
debugPrint("Could not construct data from base64")
return
}
// store the file on disk (.removingPercentEncoding removes possible URL encoded characters like "%20" for blank)
let localFileURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename.removingPercentEncoding ?? filename)
do {
try data!.write(to: localFileURL);
} catch {
debugPrint(error)
return
}
// and display it in QL
DispatchQueue.main.async {
self.documentUrl = localFileURL
self.documentPreviewController.refreshCurrentPreviewItem()
self.present(self.documentPreviewController, animated: true, completion: nil)
}
}
/*
Implementation for QLPreviewControllerDataSource
*/
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
return documentUrl as QLPreviewItem
}
/*
Implementation for QLPreviewControllerDataSource
We always have just one preview item
*/
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
/*
Checks if the given url points to a document download url
*/
private func openInDocumentPreview(_ url : URL) -> Bool {
// this is specific for our application - can be everything in your application
return url.absoluteString.contains("/APP/connector")
}
/*
Intercept the download of documents in webView, trigger the download in JavaScript and pass the binary file to JavaScript handler in Swift code
*/
private func executeDocumentDownloadScript(forAbsoluteUrl absoluteUrl : String) {
// TODO: Add more supported mime-types for missing content-disposition headers
webView.evaluateJavaScript("""
(async function download() {
const url = '\(absoluteUrl)';
try {
// we use a second try block here to have more detailed error information
// because of the nature of JS the outer try-catch doesn't know anything where the error happended
let res;
try {
res = await fetch(url, {
credentials: 'include'
});
} catch (err) {
window.webkit.messageHandlers.jsError.postMessage(`fetch threw, error: ${err}, url: ${url}`);
return;
}
if (!res.ok) {
window.webkit.messageHandlers.jsError.postMessage(`Response status was not ok, status: ${res.status}, url: ${url}`);
return;
}
const contentDisp = res.headers.get('content-disposition');
if (contentDisp) {
const match = contentDisp.match(/(^;|)\\s*filename=\\s*(\"([^\"]*)\"|([^;\\s]*))\\s*(;|$)/i);
if (match) {
filename = match[3] || match[4];
} else {
// TODO: we could here guess the filename from the mime-type (e.g. unnamed.pdf for pdfs, or unnamed.tiff for tiffs)
window.webkit.messageHandlers.jsError.postMessage(`content-disposition header could not be matched against regex, content-disposition: ${contentDisp} url: ${url}`);
}
} else {
window.webkit.messageHandlers.jsError.postMessage(`content-disposition header missing, url: ${url}`);
return;
}
if (!filename) {
const contentType = res.headers.get('content-type');
if (contentType) {
if (contentType.indexOf('application/json') === 0) {
filename = 'unnamed.pdf';
} else if (contentType.indexOf('image/tiff') === 0) {
filename = 'unnamed.tiff';
}
}
}
if (!filename) {
window.webkit.messageHandlers.jsError.postMessage(`Could not determine filename from content-disposition nor content-type, content-dispositon: ${contentDispositon}, content-type: ${contentType}, url: ${url}`);
}
let data;
try {
data = await res.blob();
} catch (err) {
window.webkit.messageHandlers.jsError.postMessage(`res.blob() threw, error: ${err}, url: ${url}`);
return;
}
const fr = new FileReader();
fr.onload = () => {
window.webkit.messageHandlers.openDocument.postMessage(`${filename};${fr.result}`)
};
fr.addEventListener('error', (err) => {
window.webkit.messageHandlers.jsError.postMessage(`FileReader threw, error: ${err}`)
})
fr.readAsDataURL(data);
} catch (err) {
// TODO: better log the error, currently only TypeError: Type error
window.webkit.messageHandlers.jsError.postMessage(`JSError while downloading document, url: ${url}, err: ${err}`)
}
})();
// null is needed here as this eval returns the last statement and we can't return a promise
null;
""") { (result, err) in
if (err != nil) {
debugPrint("JS ERR: \(String(describing: err))")
}
}
}
var toolbars: [UIView] = []
var observations : [NSKeyValueObservation] = []
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.toolbar.isHidden = false
if let navigationToobar = navigationController?.toolbar {
let observation = navigationToobar.observe(\.isHidden) {[weak self] (changedToolBar, change) in
if self?.navigationController?.toolbar.isHidden == true {
self?.navigationController?.toolbar.isHidden = false
}
}
observations.append(observation)
}
toolbars = toolbarsInSubviews(forView: view)
for toolbar in toolbars {
toolbar.isHidden = false
let observation = toolbar.observe(\.isHidden) { (changedToolBar, change) in
if let isHidden = change.newValue,
isHidden == true {
changedToolBar.isHidden = false
}
}
observations.append(observation)
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
//This hides the share item
if let add = self.children.first as? UINavigationController {
if let layoutContainerView = add.view.subviews[1] as? UINavigationBar {
layoutContainerView.subviews[2].subviews[1].isHidden = false
}
}
}
private func toolbarsInSubviews(forView view: UIView) -> [UIView] {
var toolbars: [UIView] = []
for subview in view.subviews {
if subview is UIToolbar {
toolbars.append(subview)
}
toolbars.append(contentsOf: toolbarsInSubviews(forView: subview))
}
return toolbars
}
}
You've done an excellent job getting this far. A small adjustment will fix your code. The callback needs to be inside a method matching the actual callback
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)
Move the canGoBack code as follows:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if webView.canGoBack {
let backButton = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(backButtonPressed))
navigationItem.rightBarButtonItem = backButton
} else {
self.navigationItem.rightBarButtonItem = nil
}
}
And the method it calls can look like this (based on the link you provided):
#objc
func backButtonPressed() {
if webView.canGoBack {
webView.goBack()
} else {
self.navigationController?.popViewController(animated: true)
}
}
Edit: (this is how to embed your controller inside a UINavigationController)
I've made an assumption that you're using Interface Builder. (This isn't how I would do this, but it should work for you).
Click on LaunchScreen.storyboard (in the left side column)
Click on and expand the View Controller Scene (the next column to the right at the top)
Click on the ViewController
Move to the top of the screen and select from the menu:
/Editor/Embed In/Navigation Controller
That should be all you need to do. But look up navigation controllers and learn how they work/why and when you need them.
They're very powerful, and as you've learned, they're essential!
🍀

RPScreenRecorder.shared().isAvailable is always false

I'm trying to record my screen with a sample ios application.
But it does not work because RPScreen.shared().isAvailable always returns false.
These are my codes:
ViewController.swift
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var StartRecordingButton: UIButton!
#IBOutlet weak var EndRecordingButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
StartRecordingButton.addTarget(self, action: #selector(startRecord(_:)), for: .touchUpInside)
EndRecordingButton.addTarget(self, action: #selector(stopRecord(_:)), for: .touchUpInside)
}
private lazy var recorder: ScreenRecorder = ScreenRecorder(configuration: ScreenRecorder.Configuration(), completion: {
(url, error) in
guard let url = url else {
fatalError("\(#function) record failed \(String(describing: error))")
}
debugPrint(#function, "success", url)
})
#objc func startRecord(_ sender: UIButton) {
recordStart()
}
#objc func stopRecord(_ sender: UIButton) {
recordStop()
}
private func recordStart() {
guard !recorder.isRecording else { return }
do {
try recorder.start()
} catch {
fatalError("start recording failed \(error)")
}
}
private func recordStop() {
guard recorder.isRecording else { return }
do {
try recorder.end()
} catch {
fatalError("finish recording failed \(error)")
}
}
}
ScreenRecorder.swift
import ReplayKit
#available(iOS 11.0, *)
public class ScreenRecorder: NSObject {
let screenRecorder = RPScreenRecorder.shared()
// Alias for arguments
public typealias Completion = (URL?, Error?) -> ()
let completion: Completion
let configuration: Configuration
public init (configuration: Configuration, completion: #escaping Completion) {
self.configuration = configuration
self.completion = completion
super.init()
}
// Start recording screen
public func start() throws {
print(screenRecorder.isAvailable)
guard screenRecorder.isAvailable else {
throw ScreenRecorderError.notAvailable
}
guard !screenRecorder.isRecording else {
throw ScreenRecorderError.alreadyRunning
}
try setUp()
assetWriter?.startWriting()
assetWriter?.startSession(atSourceTime: CMTime.zero)
screenRecorder.startCapture(handler: { [weak self] (cmSampleBuffer, rpSampleBufferType, error) in
if let error = error {
debugPrint(#function, "something happened", error)
}
if RPSampleBufferType.video == rpSampleBufferType {
self?.appendVideo(sampleBuffer: cmSampleBuffer)
}
}) { [weak self] (error) in
if let error = error {
self?.completion(nil, error)
}
}
}
public func end() throws {
guard screenRecorder.isRecording else {
throw ScreenRecorderError.notRunning
}
screenRecorder.stopCapture { [weak self] (error) in
if let error = error {
self?.completion(nil, error)
}
self?.videoAssetWriterInput?.markAsFinished()
self?.assetWriter?.finishWriting {
DispatchQueue.main.async {
self?.completion(self?.cacheFileURL, nil)
}
}
}
}
public var isRecording: Bool {
return screenRecorder.isRecording
}
private var startTime: CMTime?
private var assetWriter: AVAssetWriter?
private var videoAssetWriterInput: AVAssetWriterInput?
private var writerInputPixelBufferAdapter: AVAssetWriterInputPixelBufferAdaptor?
private func setUp() throws {
try createCacheDirectoryIfNeeded()
try removeOldCachedFile()
guard let cacheURL = cacheFileURL else {
throw ScreenRecorderError.invalidURL
}
let assetWriter = try AVAssetWriter(url: cacheURL, fileType: configuration.fileType)
let videoSettings: [String: Any] = [
AVVideoCodecKey: configuration.codec,
AVVideoWidthKey: UInt(configuration.videoSize.width),
AVVideoHeightKey: UInt(configuration.videoSize.height),
]
let videoAssetWriterInput = try AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
videoAssetWriterInput.expectsMediaDataInRealTime = true
if assetWriter.canAdd(videoAssetWriterInput) {
assetWriter.add(videoAssetWriterInput)
}
self.assetWriter = assetWriter
self.videoAssetWriterInput = videoAssetWriterInput
self.writerInputPixelBufferAdapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoAssetWriterInput, sourcePixelBufferAttributes: [
kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32ARGB)
])
}
private func appendVideo(sampleBuffer: CMSampleBuffer) {
guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return }
let firstTime: CMTime
if let startTime = self.startTime {
firstTime = startTime
} else {
firstTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
startTime = firstTime
}
let currentTime: CMTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer)
let diffTime: CMTime = CMTimeSubtract(currentTime, firstTime)
if writerInputPixelBufferAdapter?.assetWriterInput.isReadyForMoreMediaData ?? false {
writerInputPixelBufferAdapter?.append(pixelBuffer, withPresentationTime: diffTime)
}
}
private func createCacheDirectoryIfNeeded() throws {
guard let cacheDirectoryURL = cacheDirectoryURL else { return }
let fileManager = FileManager.default
guard !fileManager.fileExists(atPath: cacheDirectoryURL.path) else { return }
try fileManager.createDirectory(at: cacheDirectoryURL, withIntermediateDirectories: true, attributes: nil)
}
private func removeOldCachedFile() throws {
guard let cacheURL = cacheFileURL else { return }
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: cacheURL.path) else { return }
try fileManager.removeItem(at: cacheURL)
}
private var cacheDirectoryURL: URL? = {
guard let path = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first else {
return nil
}
return URL(fileURLWithPath: path).appendingPathComponent("ScreenRecorder")
}()
private var cacheFileURL: URL? {
guard let cacheDirectoryURL = cacheDirectoryURL else { return nil }
return cacheDirectoryURL.appendingPathComponent("screenrecord.mp4")
}
}
#available(iOS 11.0, *)
extension ScreenRecorder {
public struct Configuration{
public var codec: AVVideoCodecType = .h264
public var fileType: AVFileType = .mp4
public var videoSize: CGSize = CGSize(
width: UIScreen.main.bounds.width,
height: UIScreen.main.bounds.height
)
public var audioQuality: AVAudioQuality = .medium
public var audioFormatID: AudioFormatID = kAudioFormatMPEG4AAC
public var numberOfChannels: UInt = 2
public var sampleRate: Double = 44100.0
public var bitrate: UInt = 16
public init() {}
}
public enum ScreenRecorderError: Error {
case notAvailable
case alreadyRunning
case notRunning
case invalidURL
}
}
And it shows this fatal error which I wrote:
ios_record_screen[1258:213516] Fatal error: start recording failed notAvailable
I've enabled screen recording in Settings app in my iPhone8, and tried to run on my friend's iPhone X as well.
But both phones didn't work...
I could not find helpful information in the Internet.
Hope a help.
I hope the problem for those who struggled before has been resolved
In my case,
override func viewDidLoad()
needed
RPScreenRecorder.shared().delegate = self
syntax.
Of course, even the delegate extension that comes with it.
I was implementing RPScreenRecorder in a new view, which was working normally in other views, and I encountered the same problem as the author in the process.
It was a problem that the delegate was not imported while looking for a difference from the previous one.
Hope this helps anyone who finds this page in the future.

Index related error in retrieving the data from Firestore database

I am not able to load the documents in chat application in Swift IOS using Firestore database, though able to successfully retrieve the data from the Firestore database, I have added the deinit method as well please assist further to resolve the error, I have added the complete view controller , please help me
Error
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (47) must be equal to the number of rows contained in that section before the update (23), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Code
let kBannerAdUnitID = "ca-app-pub-3940256099942544/2934735716"
#objc(FCViewController)
class FCViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,
UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// Instance variables
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var sendButton: UIButton!
var ref : CollectionReference!
var ref2: DocumentReference!
var messages: [DocumentSnapshot]! = []
var msglength: NSNumber = 10
fileprivate var _refHandle: CollectionReference!
var storageRef: StorageReference!
var remoteConfig: RemoteConfig!
private let db = Firestore.firestore()
private var reference: CollectionReference?
private let storage = Storage.storage().reference()
// private var messages = [Constants.MessageFields]()
//snapshot private var messages: [Constants.MessageFields] = []
private var messageListener: ListenerRegistration?
// var db:Firestore!
#IBOutlet weak var banner: GADBannerView!
#IBOutlet weak var clientTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.clientTable.register(UITableViewCell.self, forCellReuseIdentifier: "tableViewCell")
// clientTable.delegate = self
//clientTable.dataSource = self
//db = Firestore.firestore()
ref = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages")
ref2 = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").document()
configureDatabase()
configureStorage()
configureRemoteConfig()
fetchConfig()
loadAd()
}
deinit {
if let refhandle = _refHandle {
let listener = ref.addSnapshotListener { querySnapshot, error in
}
listener.remove()
}
}
func configureDatabase() {
db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
/* let name = documents.map { $0["name"]!}
let text = documents.map { $0["text"]!}
let photourl = documents.map { $0["photoUrl"]!}
print(name)
print(text)
print(photourl)*/
self.messages.append(contentsOf: documents)
// self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
//self.clientTable.reloadData()
}
}
func configureStorage() {
storageRef = Storage.storage().reference()
}
func configureRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
let remoteConfigSettings = RemoteConfigSettings(developerModeEnabled: true)
remoteConfig.configSettings = remoteConfigSettings
}
func fetchConfig() {
var expirationDuration: Double = 3600
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if self.remoteConfig.configSettings.isDeveloperModeEnabled {
expirationDuration = 0
}
remoteConfig.fetch(withExpirationDuration: expirationDuration) { [weak self] (status, error) in
if status == .success {
print("Config fetched!")
guard let strongSelf = self else { return }
strongSelf.remoteConfig.activateFetched()
let friendlyMsgLength = strongSelf.remoteConfig["friendly_msg_length"]
if friendlyMsgLength.source != .static {
strongSelf.msglength = friendlyMsgLength.numberValue!
print("Friendly msg length config: \(strongSelf.msglength)")
}
} else {
print("Config not fetched")
if let error = error {
print("Error \(error)")
}
}
}
}
#IBAction func didPressFreshConfig(_ sender: AnyObject) {
fetchConfig()
}
#IBAction func didSendMessage(_ sender: UIButton) {
_ = textFieldShouldReturn(textField)
}
#IBAction func didPressCrash(_ sender: AnyObject) {
print("Crash button pressed!")
Crashlytics.sharedInstance().crash()
}
func inviteFinished(withInvitations invitationIds: [String], error: Error?) {
if let error = error {
print("Failed: \(error.localizedDescription)")
} else {
print("Invitations sent")
}
}
func loadAd() {
self.banner.adUnitID = kBannerAdUnitID
self.banner.rootViewController = self
self.banner.load(GADRequest())
}
// UITableViewDataSource protocol methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot: DocumentSnapshot! = self.messages[indexPath.row]
guard let message = messageSnapshot as? [String:String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
if imageURL.hasPrefix("gs://") {
Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
DispatchQueue.main.async {
cell.imageView?.image = UIImage.init(data: data!)
cell.setNeedsLayout()
}
}
} else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage.init(data: data)
}
cell.textLabel?.text = "sent by: \(name)"
} else {
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
}
return cell
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let text = textField.text else { return true }
textField.text = ""
view.endEditing(true)
let data = [Constants.MessageFields.text: text]
sendMessage(withData: data)
return true
}
func sendMessage(withData data: [String: String]) {
var mdata = data
mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
if let photoURL = Auth.auth().currentUser?.photoURL {
mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
}
// Push data to Firebase Database
self.ref.document().setData(mdata, merge: true) { (err) in
if let err = err {
print(err.localizedDescription)
}
print("Successfully set newest city data")
}
}
// MARK: - Image Picker
#IBAction func didTapAddPhoto(_ sender: AnyObject) {
let picker = UIImagePickerController()
picker.delegate = self
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
picker.sourceType = .camera
} else {
picker.sourceType = .photoLibrary
}
present(picker, animated: true, completion:nil)
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion:nil)
guard let uid = Auth.auth().currentUser?.uid else { return }
// if it's a photo from the library, not an image from the camera
if #available(iOS 8.0, *), let referenceURL = info[.originalImage] as? URL {
let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL
let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
guard let strongSelf = self else { return }
strongSelf.storageRef.child(filePath)
.putFile(from: imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
let nsError = error as NSError
print("Error uploading: \(nsError.localizedDescription)")
return
}
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
})
} else {
guard let image = info[.originalImage] as? UIImage else { return }
let imageData = image.jpegData(compressionQuality:0.8)
let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
self.storageRef.child(imagePath)
.putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
guard let strongSelf = self else { return }
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
#IBAction func signOut(_ sender: UIButton) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
dismiss(animated: true, completion: nil)
} catch let signOutError as NSError {
print ("Error signing out: \(signOutError.localizedDescription)")
}
}
func showAlert(withTitle title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title,
message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alert.addAction(dismissAction)
self.present(alert, animated: true, completion: nil)
}
}
}
Edit
perform this block of code on main thread
for doc in documents {
self.messages.append(doc)
self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
}
This should work..

Completion Handlers in swift 4

I have a problem I'm trying to wrap my head around relating to the use of completion handlers. I have 3 layers in my iOS program, the ViewController->Service->Networking. I need to load some data through API call from the view controller.
I have defined functions(completionHandlers) in the ViewController that should execute once the data request is complete and am comfortable when in implementing completion handlers when only two layers exists, but confused when in the following scenario:
DashboardViewController.swift
import UIKit
#IBDesignable class DashboardViewController: UIViewController {
#IBOutlet weak var stepCountController: ExpandedCardView!
var articles:[Article]?
let requestHandler = RequestHandler()
let dashboardService = DashboardService()
override func viewDidLoad() {
super.viewDidLoad()
dashboardService.getDashboardData(completionHandler: getDashboardDataCompletionHandler)
}
func getDashboardDataCompletionHandler(withData: DashboardDataRequest) {
print(withData)
}
}
DashboardService.swift
import Foundation
class DashboardService: GeneralService {
var requestHandler: DashboardRequestHandler
override init() {
requestHandler = DashboardRequestHandler()
super.init()
}
//this function should execute requestHandler.requestDashboardData(), and then execute convertDashboardData() with the result of previous get request
func getDashboardData(completionHandler: #escaping (DashboardDataRequest) -> Void) {
//on network call return
guard let url = URL(string: apiResourceList?.value(forKey: "GetDashboard") as! String) else { return }
requestHandler.requestDashboardData(url: url, completionHandler: convertDashboardData(completionHandler: completionHandler))
}
func convertDashboardData(completionHandler: (DashboardDataRequest) -> Void) {
//convert object to format acceptable by view
}
}
DashboardRequestHandler.swift
import Foundation
class DashboardRequestHandler: RequestHandler {
var dataTask: URLSessionDataTask?
func requestDashboardData(url: URL, completionHandler: #escaping (DashboardDataRequest) -> Void) {
dataTask?.cancel()
defaultSession.dataTask(with: url, completionHandler: {(data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else {
return
}
do {
let decodedJson = try JSONDecoder().decode(DashboardDataRequest.self, from: data)
completionHandler(decodedJson)
} catch let jsonError {
print(jsonError)
}
}).resume()
}
}
If you look at the comment in DashboardService.swift, my problem is quite obvious. I pass a completion handler from ViewController to Service and Service has its own completion handler that it passes to RequestHandler where the view controller completion handler (getDashboardDataCompletionHandler) should be executed after the Service completion handler (convertDashboardData())
Please help me in clarifying how to implement this. Am I making a design mistake by trying to chain completionHandlers like this or am I missing something obvious.
Thank you
--EDIT--
My Request Handler implementation is as follows:
import Foundation
class RequestHandler {
// let defaultSession = URLSession(configuration: .default)
var defaultSession: URLSession!
init() {
guard let path = Bundle.main.path(forResource: "Api", ofType: "plist") else {
print("Api.plist not found")
return
}
let apiResourceList = NSDictionary(contentsOfFile: path)
let config = URLSessionConfiguration.default
if let authToken = apiResourceList?.value(forKey: "AuthToken") {
config.httpAdditionalHeaders = ["Authorization": authToken]
}
defaultSession = URLSession(configuration: config)
}
}
In this case is more clear to use delegation, for example like this:
protocol DashboardServiceDelegate {
func didLoaded(_ viewModel: DashboardViewModel)
}
class DashboardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
dashboardService.delegate = self
dashboardService.getDashboardData()
}
}
extension DashboardViewController: DashboardServiceDelegate {
func didLoaded(_ viewModel: DashboardViewModel) {
///
}
}
protocol DashboardRequestHandlerDelegate() {
func didLoaded(request: DashboardDataRequest)
}
class DashboardService: GeneralService {
private lazy var requestHandler: DashboardRequestHandler = { reqHandler in
reqHandler.delegate = self
return reqHandler
}(DashboardRequestHandler())
func getDashboardData() {
guard let url = ...
requestHandler.requestDashboardData(url: url)
}
}
extension DashboardService: DashboardRequestHandlerDelegate {
func didLoaded(request: DashboardDataRequest) {
let viewModel = convert(request)
delegate.didLoaded(viewModel)
}
}

Resources