Xcode 13 redundant superclass - uiviewcontroller

protocol HasWaitAnimation: UIViewController {
var alertWait: ActivityViewController? {get set}
}
extension HasWaitAnimation where Self: UIViewController {
func showAlertWait(message: String, completion: #escaping()->Void) {
DispatchQueue.main.async {
let activitiyViewController = ActivityViewController(message: message)
self.present(activitiyViewController, animated: false, completion: {
completion()
})
self.alertWait = activitiyViewController
}
}
func hideAlertWait (completion: #escaping () -> Void) {
DispatchQueue.main.asyncAfter(deadline: .now()+0.2, execute: {
self.alertWait?.dismiss(animated: true, completion: completion)
self.alertWait = nil
})
}
}
On Xcode 13 I get
"Redundant superclass constraint 'Self' : 'UIViewController'"
at line
"extension HasWaitAnimation where Self: UIViewController {"
How can I solve this?

Related

How to get data from an API using Swift

I'm trying to get data from an API into my table view, but app goes into the catch error " json error". I'll share the code with you.
class ViewController: UIViewController {
#IBOutlet weak var homeTableView: UITableView!
var repository = [RepositoryStats]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON {
self.homeTableView.reloadData()
}
homeTableView.delegate = self
homeTableView.dataSource = self
}
func downloadJSON (completed: #escaping () -> ()) {
let url = URL (string: "https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.repository = try JSONDecoder().decode([RepositoryStats].self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print ("json error")
}
}
}.resume()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return repository.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = repository[indexPath.row].full_name
return cell
}
This is my struct where I declared :
struct RepositoryStats: Decodable {
let items: [Item]
}
struct Item: Decodable {
let fullName: String
}
If anyone know, why I am getting into the "json error" catch ? Thank you !
Link : https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc
I fixed it. I have changed my struct into this :
struct RepositoryStats: Decodable {
let items: [Item]
}
struct Item: Decodable {
let full_name: String
let forks_count: Int
let stargazers_count: Int
}
And this is my downloaad JSON func.
func downloadJSON (completed: #escaping () -> ()) {
let url = URL (string: "https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.repository = try JSONDecoder().decode(RepositoryStats.self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print (error)
}
}
}.resume()
}
}

Load html text in WKWebView

I use this code to load my html file with text in WKWebView:
do {
guard let filePath = Bundle.main.path(forResource: "\(readBookNumber)", ofType: "html")
else {
print ("File reading error")
return
}
var content = try String(contentsOfFile: filePath, encoding: .utf8)
let baseUrl = URL(fileURLWithPath: filePath)
content.changeHtmlStyle(font: "Iowan-Old-Style", fontSize: UserDefaults.standard.integer(forKey: "textSize"), fontColor: textColor)
webView.loadHTMLString(headerString+content, baseURL: baseUrl)
}
catch {
print ("File HTML error")
}
and this code to load the page where the user stopped reading last time:
self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
I use code for loading last page in this method:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
}
}
At first I used deadline: .now() + 0.1, but that didn't work. Because the last read page was loaded initially, and after a few seconds I see my text on the first page. I change it to deadline: .now() + 0.5 and the text loads fine from the last page read. Its was 700 pages. But now I want to load another text with 1700 pages. And I have same problem like first time. I can change deadline: .now() + 1.0 and my text will load fine. But I think this is not the best solution. I run it on my iPhone X. But maybe if I run it on iPad mini 2 I should change deadline: .now() + 10.0 because iPad mini 2 not very powerful. How to solve the problem?
Update based on #DPrice code:
If I use this code:
override func viewDidLoad() {
super.viewDidLoad()
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
....
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == "estimatedProgress") {
if webView.estimatedProgress == 1.0 {
self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad\(self.readBookNumber)"))
}
}
}
I have same bad result like in my code.
But if I use this code:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == "estimatedProgress") {
if webView.estimatedProgress == 1.0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad\(self.readBookNumber)"))
}
}
}
}
Everything works fine. And my last page loading fine. But it does not solve the problem in my question.
Here is a modified version of your ViewController class:
import UIKit
import WebKit
class ViewController: UIViewController, UIScrollViewDelegate, WKNavigationDelegate {
#IBOutlet weak var webView: WKWebView!
#IBOutlet weak var pagesLabel: UILabel!
var readBookNumber = 0
let headerString = "<meta name=\"viewport\" content=\"initial-scale=1.0\" />"
var textSize = 3
var contentSize: CGSize = .zero
override func viewDidLoad() {
super.viewDidLoad()
// Web View Delegate
webView.scrollView.delegate = self
webView.navigationDelegate = self
webView.scrollView.isPagingEnabled = true
webView.scrollView.alwaysBounceVertical = false
webView.scrollView.showsHorizontalScrollIndicator = true
webView.scrollView.showsVerticalScrollIndicator = false
webView.scrollView.panGestureRecognizer.isEnabled = false
webView.scrollView.pinchGestureRecognizer?.isEnabled = false
webView.scrollView.bouncesZoom = false
self.webView.isOpaque = false;
self.webView.backgroundColor = .clear
// Load File
do {
guard let filePath = Bundle.main.path(forResource: "0", ofType: "html")
else {
print ("File reading error")
return
}
var content = try String(contentsOfFile: filePath, encoding: .utf8)
let baseUrl = URL(fileURLWithPath: filePath)
content.changeHtmlStyle(font: "Iowan-Old-Style", fontSize: 4, fontColor: "black")
webView.loadHTMLString(headerString+content, baseURL: baseUrl)
// add content size Observer
webView.scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentSize), options: .new, context: nil)
}
catch {
print ("File HTML error")
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == #keyPath(UIScrollView.contentSize)) {
let contentSize = webView.scrollView.contentSize
if contentSize != self.contentSize {
self.contentSize = contentSize
DispatchQueue.main.async {
self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
}
}
}
}
// MARK: - webView Scroll View
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.stoppedScrolling()
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.stoppedScrolling()
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
var currentPage = Int((webView.scrollView.contentOffset.x / webView.scrollView.frame.size.width) + 1)
let pageCount = Int(webView.scrollView.contentSize.width / webView.scrollView.frame.size.width)
if currentPage == 0 {
currentPage = 1
} else {
}
if !webView.isHidden {
pagesLabel.text = "\( currentPage ) из \( pageCount )"
} else {
pagesLabel.text = ""
}
}
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
webView.scrollView.pinchGestureRecognizer?.isEnabled = false
}
func stoppedScrolling() {
let pageToLoad = Int((webView.scrollView.contentOffset.x))
UserDefaults.standard.set(pageToLoad, forKey: "pageToLoad")
}
// MARK: - loading webView
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// Маленькая задержка, которую мне хотелось бы использовать
/*DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
}*/
// Большая задержка, которую мне приходится использовать
// don't do this here... we'll do the "auto-scroll" inside the change contentSize Observer
//DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
// self.webView.scrollView.contentOffset.x = CGFloat(UserDefaults.standard.integer(forKey: "pageToLoad"))
//}
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
}
}
extension String {
mutating func changeHtmlStyle(font: String, fontSize: Int, fontColor: String) {
let style = "<font face='\(font)' size='\(fontSize)' color= '\(fontColor)'>%#"
self = String(format: style, self)
}
}
It uses an Observer to watch the contentSize change in the web view's scroll view.
Note that it is called multiple times - with different values - during the load and layout process, but it may do the job for you.
Also note, though, that you'll need to account for changes in the web view size - for example, if the user rotates the device. So... more to do, but this may get you going.
You can add a property observer and watch the estimated progress of the page load:
override func viewDidLoad() {
super.viewDidLoad()
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress), options: .new, context: nil)
....
}
and observe when the page is being loaded:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == "estimatedProgress") {
if webView.estimatedProgress == 1.0 {
print ("page loaded")
}
}
}
You may be able to predict based on the page number how far into the loading process you need to be before you set your offset.
Instead of observing WKWebView.estimatedProgress you should observe UIScrollView.contentSize because you need to scroll to an available position e.g.:
var positionY: CGFloat = 1000
var contentSize = CGSize(width: 0, height: 0)
override func viewDidLoad() {
super.viewDidLoad()
...
webView?.scrollView.addObserver(self, forKeyPath: #keyPath(UIScrollView.contentSize), options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == #keyPath(UIScrollView.contentSize)) {
if let contentSize = webView?.scrollView.contentSize, contentSize != self.contentSize {
self.contentSize = contentSize
if contentSize.height > positionY {
webView?.scrollView.setContentOffset(CGPoint(x: 0, y: positionY), animated: true)
}
}
}
}

webview executing one of both urls

I'm doing a dictionary application. Some terms have animation, some don't. If ;
let url = URL(string: "http://bsstech.site/-Sozlukler/Fizik/(f.animasyonAdi ?? "").html")!
webview.load(URLRequest(url: url))
or let url = URL(string: "http://bsstech.site/-Sozlukler/Fizik/logo.html")!
webview.load(URLRequest(url: url)) I want to run.
Did I write the code as below, but I did not get the result I wanted.
if let f = fizik {
if (f.animasyonAdi != nil) {
let url = URL(string: "http://bsstech.site/-Sozlukler/Fizik/\(f.animasyonAdi ?? "").html")!
webview.load(URLRequest(url: url))
}else {
let url = URL(string: "http://bsstech.site/-Sozlukler/Fizik/logo.html")!
webview.load(URLRequest(url: url))
}
navigationItem.title = f.baslik
aciklama.text = f.aciklama
}
}
I would be very glad if you help.
Step 1: Create a WebViewViewController
Step 2: Added WebKitView, top title label, a cross button and activity IndicatorView in the WebViewVC.xib file, then insert outlets in the WebViewVC.swift
Step 3: Implement logic in the WebViewVC.swift like the following:
import UIKit
import WebKit
class WebViewVC: UIViewController {
// MARK: - Outlets
#IBOutlet private weak var webView: WKWebView!
#IBOutlet private weak var activityIndicatorView: UIActivityIndicatorView!
#IBOutlet private weak var titleLabel: UILabel!
// MARK: - Variables
private let userAgentValue = "Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16"
var navTitle: String?
var urlString: String?
// MARK: - View Cycle
override func viewDidLoad() {
super.viewDidLoad()
initView()
setupWebView()
loadData()
}
// MARK: - Event
#IBAction private func actionTapToCloseButton(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
// MARK: - Setting up View Controller
extension WebViewVC {
private func initView() {
titleLabel.text = navTitle
}
private func setupWebView() {
webView.navigationDelegate = self
webView.customUserAgent = userAgentValue
webView.isMultipleTouchEnabled = true
webView.isUserInteractionEnabled = true
}
private func loadData() {
if let `urlString` = urlString, !urlString.isEmpty, let query = urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed), let url = URL(string: query) {
let request = URLRequest(url: url)
webView.load(request)
}
}
}
// MARK: - WKNavigationDelegate
extension WebViewVC: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
activityIndicatorView.startAnimating()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
activityIndicatorView.stopAnimating()
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
ShowPopUps.showDefaultAlert(title: "", message: "\(error.localizedDescription)", negativeActionText: "Ok")
activityIndicatorView.stopAnimating()
}
}
Step 4: Just Call
if let f = fizik {
var urlString: String? = nil
if (f.animasyonAdi != nil) {
urlString = http://bsstech.site/-Sozlukler/Fizik/\(f.animasyonAdi ?? "").html"
} else {
urlString = "http://bsstech.site/-Sozlukler/Fizik/logo.html"
}
let vc = WebViewVC()
vc.urlString = urlString
vc.navTitle = f.baslik
present(vc, animated: true, completion: nil)
}

Failed when present UIviewcontroller

I have 2 viewcontrollers: mainVC & listVC.
I tapped customized button on mainVC to present listVC, then tapped button on listVC to go back to mainVC. until now there is no any failure. But when I tap customized button to go to listVC again, program failed.
error:
NSInvalidArgumentException', reason: 'Application tried to present modal view controller on itself. Presenting controller is
Voice_of_Animals.ListVC: 0x7feeee513210>
class ViewController: UIViewController
{
var cb = CirButton(Circolor: UIColor.cyan, Rsize: 100, PositionX: 100, PositionY: 100)
override func viewDidLoad() {
super.viewDidLoad()
mainVC = self
self.view.addSubview(cb)
listVC.transitioningDelegate = listVC as UIViewControllerTransitioningDelegate
}
func showNextPage() {
self.present(listVC, animated: true, completion: nil)
}
#IBAction func show(_ sender: UIButton) {
self.present(listVC, animated: true, completion: nil)
}
#IBAction func Touch(_ sender: CButton) {
sender.RunAnimation()
}
override func didReceiveMemoryWarning() {
}
}
class ListVC: ViewController{
var transition = FadeAnimator()
var btn1 = CirButton(Circolor: UIColor.cyan, Rsize: 100, PositionX: 100, PositionY: 100)
#IBOutlet weak var myList: ListView!
#IBAction func Push(_ sender: UIButton) {
}
#IBAction func edgeSlide(_ sender: UIScreenEdgePanGestureRecognizer) {
mainVC.dismiss(animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(btn1)
}
}

How to make alert on Alamofire request swift 3

Hey I am trying to make alert message with error information from alamofire request, but it should display no matter which view is active, because this request is on separated class, How I can do that?
Request class:
class Json {
var loginToken = ""
public func login(userName: String, password: String, loginCompletion: #escaping (_ JSONResponse : Any?, _ error: Error?) -> ()) {
let loginrequest = JsonRequests.loginRequest(userName: userName, password: password)
makeWebServiceCall(urlAddress: URL, requestMethod: .post, params: loginrequest, completion: { (json, error) in
loginCompletion(json, error)
})
}
public func device(token: String, loginCompletion: #escaping (_ JSONResponse : Any?, _ error: Error?) -> ()) {
let deviceinfo = JsonRequests.getInformationFromConfig(token: token, config: "wireless", section: "#wifi-iface[0]", option: "mode")
makeWebServiceCall(urlAddress: URL, requestMethod: .post, params: deviceinfo, completion: { (json, error) in
loginCompletion(json, error)
})
}
let manager = Alamofire.SessionManager.default
private func makeWebServiceCall (urlAddress: String, requestMethod: HTTPMethod, params:[String:Any], completion: #escaping (_ JSONResponse : Any?, _ error: Error?) -> ()) {
manager.session.configuration.timeoutIntervalForRequest = 5
manager.request(urlAddress, method: requestMethod, parameters: params, encoding: JSONEncoding.default).responseJSON{ response in
print(response.timeline)
switch response.result {
case .success(let value):
let json = JSON(value)
if let message = json["error"]["message"].string, message == "Access denied" {
// LoginVC.performLogin(UserDefaults.standard.value(forKey: "saved_username"),UserDefaults.standard.value(forKey: "saved_password"))
print("Access denied")
}
if let jsonData = response.result.value {
completion(json, nil)
}
case .failure(let error):
completion(nil, error)
}
Call function:
public class LoginModel {
var loginToken = ""
internal func JsonResult (param1: String, param2: String){
Json().login(userName: param1, password: param2) { (json, error) in
print(json)
print(error)
if error != nil {
//Show alert
return
}
//Access JSON here
if let jsonResponse = json {
let jsonDic = JSON(jsonResponse)
print(jsonDic)
//Now access jsonDic to get value from the response
for item in jsonDic["result"].arrayValue {
self.loginToken = item["ubus_rpc_session"].stringValue}
print(self.loginToken)
if (jsonDic["result"].exists()){
print(jsonDic["result"]["ubus_rpc_session"].stringValue)
if (jsonDic["result"].arrayValue.contains("6")) {
self.loginToken = "6"
} else {
for item in jsonDic["result"].arrayValue {
self.loginToken = item["ubus_rpc_session"].stringValue
UserDefaults.standard.setValue(self.loginToken, forKey: "saved_token")
print(self.loginToken)
}
}
}
print("No result")
}
}
self.JsonDevice(param1: (UserDefaults.standard.value(forKey: "saved_token")! as! String))
}
If you want to pass the error and show the alert from calling controller then you can use add one more type with your completionHandler.
So instead of making completionHandler with type completion: #escaping (_ JSON : Any) make it like this completion: #escaping (_ JSONResponse : Any?, _ error: Error?).
Now when you get response with your api then Error is nil so call completion handler like this way.
completion(json, nil)
When you get failure response then pass nil as response with error.
completion(nil, error)
Now when you call this function check for the error.
retur.login(userName: userName.text!, password: password.text!) { (json, error) in {
if error != nil {
//Show alert
return
}
//Access JSON here
if let jsonDic = json as? JSON {
//Now access jsonDic to get value from the response
print(jsonDic["field_that_you_want_to_access"])
}
}
First create global func:
func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController , top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
Than u can do something like this:
struct ErrorShower {
static func showErrorWith(title:String? = nil, message:String? = nil, complition:(() -> ())?){
if let _ = topViewController() as? UIAlertController {
return
}
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { _ in
complition?()
}))
topViewController()?.present(alert, animated: true, completion: nil)
}
}
And simple call from where u want:
ErrorShower.showErrorWith(message: err.message, complition: nil)