Load WebView before transitioning to it - ios

I had to update an app that i didn't touch for quite a while and now am facing an 'Fatal error: Unexpectedly found nil while unwrapping an Optional value' in DetailViewController::refreshWebView() when executing webView.loadRequest(myRequest) because webView is nil at this point in time.
I didn't change anything relating to how the DetailViewController is loaded and assigned to MasterViewController, so I am very confused as why this does not work anymore.
Was there something changed that i am not aware of? Or did I implement this whole thing incorrectly in the first place and it was coincidence that it worked?
import UIKit
protocol EventSelectionDelegate: class {
func eventSelected(_ newEvent: String)
}
class MasterViewController: UIViewController, UIWebViewDelegate {
var activityView: UIActivityIndicatorView!
weak var delegate: EventSelectionDelegate?
func detailChosen(_ detailUrlString: String) {
delegate?.eventSelected(detailUrlString)
activityView.startAnimating()
}
func transitionToDetail() {
if let detailViewController = self.delegate as? DetailViewController {
DispatchQueue.main.async {
self.activityView.stopAnimating()
self.splitViewController?.showDetailViewController(detailViewController, sender: nil)
}
}
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
import UIKit
class DetailViewController: UIViewController, UIWebViewDelegate {
#IBOutlet weak var webView: UIWebView!
var animationWaitDelegate: MasterViewController!
var eventUrl: String! {
didSet {
self.refreshWebView()
}
}
override func viewDidLoad() {
super.viewDidLoad()
webView.delegate = self
}
func refreshWebView() {
let myURL = URL(string:eventUrl!)
let myRequest = URLRequest(url: myURL!)
webView.loadRequest(myRequest)
}
func webViewDidFinishLoad(_ webView: UIWebView) {
animationWaitDelegate.transitionToDetail()
}
}
extension DetailViewController: EventSelectionDelegate {
func eventSelected(_ newEvent: String) {
eventUrl = newEvent
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
PS: I found a workaround in the meantime where I added a flag in DetailViewController that allows me to call refreshWebView in viewDidLoad if webView was nil the first time it was called.

First you have to update following code for null check, which will prevent crashing your app.
func refreshWebView() {
if webView != nil {
let myURL = URL(string:eventUrl!)
let myRequest = URLRequest(url: myURL!)
webView.loadRequest(myRequest)
}
}
After at transition add following code will fix your issue.
func transitionToDetail() {
if let detailViewController = self.delegate as? DetailViewController {
DispatchQueue.main.async {
self.activityView.stopAnimating()
detailViewController.loadViewIfNeeded() // Add this line of code
self.splitViewController?.showDetailViewController(detailViewController, sender: nil)
}
}
}
loadViewIfNeeded() method will load your controls before open screen if required.

Related

ios Swift Protocol Data

I don't use storyboards.
I want to send protocol data using #objc button action.
However, the sent view controller does not run the protocol function.
May I know what the reason is?
In fact, there's a lot more code.
Others work, but only protocol functions are not executed.
The didUpdataChampion function is
Data imported into a different protocol.
I have confirmed that there is no problem with this.
protocol MyProtocolData {
func protocolData(dataSent: String)
func protocolCount(dataInt: Int)
}
class PickViewController: UIViewController,ChampionManagerDelegate{
static let identifier = "PickViewController"
var count = 0
var urlArray = [URL]()
var pickDelegate : MyProtocolData?
override func viewDidLoad() {
super.viewDidLoad()
champions.riot(url: "myURL")
}
#objc func topHand(){
pickDelegate?.protocolData(dataSent: "top")
print(count)
pickDelegate?.protocoCount(dataInt: count)
let cham = ChampViewController()
cham.modalPresentationStyle = .fullScreen
present(cham, animated: true, completion: nil)
}
//Data imported to another protocol
func didUpdataChampion(_ championManager: ChampionManager, champion: [ChampionRiot]) {
print(#function)
count = champion.count
for data in champion {
let id = data.id
guard let url = URL(string: "https://ddragon.leagueoflegends.com/cdn/11.16.1/img/champion/\(id).png") else { return }
urlArray.append(url)
count = urlArray.count
}
}
func didFailWithError(error: Error) {
print(error)
}
}
class ChampViewController: UIViewController,MyProtocolData {
var pickData = ""
var arrayCount = 0
override func viewDidLoad() {
super.viewDidLoad()
}
func protocolData(dataSent: String) {
print(#function)
pickData = dataSent
print(pickData)
}
func protocoCount(dataInt: Int) {
print(#function)
arrayCount = dataInt
print(arrayCount)
}
}
i don't see full code, for instance how you call bind to topHand(), my advice is:
check that topHand - is called
check that pickDelegate isn't nil inside topHand
Create Object fo your PickViewController class and set its delegate to self.
var yourObj = PickViewController()
override func viewDidLoad() {
super.viewDidLoad()
yourObj.delegate = self
}

Cannot transfer Data from VC1 to VC2 using protocols in Swift

well, im using 2 VCs, one with a textField where the user inputs the CityName, and another VC where it takes care of all the UI elements(like the temp, cityname, etc..), now I use also a NetWorkManager to take care of all the networking&JSON stuff.
the problem is im trying to transfer the data from the NetWorkManager to VC1 but for some reason the delegate aint working :( - basically the road should be like this : VC2 -> NetWorkManager -> VC1.
Here's my Code:
import Foundation
protocol NetworkManagerDelegate {
func didUpdateWeather(weather: WeatherModel)
}
struct NetworkManager {
let weatherURL = "https://api.openweathermap.org/data/2.5/weather?appid=2da9980c9a43e21c2cdb1f28316d151d&units=metric"
var delegate: NetworkManagerDelegate?
func fetchWeather(cityName: String) {
let urlString = "\(weatherURL)&q=\(cityName)"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
if error != nil {
print(error!)
}
if let safeData = data {
if let weather = self.parseJSON(weatherData: safeData) {
print("Im not nil")
self.delegate?.didUpdateWeather(weather: weather)
}
}
}
task.resume()
}
}
func parseJSON(weatherData: Data) -> WeatherModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(WeatherDataModel.self, from: weatherData)
let id = decodedData.weather[0].id
let cityName = decodedData.name
let temp = decodedData.main.temp
let weather = WeatherModel(conditionId: id, cityName: cityName ,temperatrue: temp)
print("Temp is: \(weather.temperatrueString)")
return weather
} catch {
print(error)
return nil
}
}
}
VC2:
import UIKit
import Foundation
class WeatherByCityController: UIViewController, UITextFieldDelegate {
// func didUpdateWeather(weather: WeatherModel) {
// print("Hi")
// }
//
#IBOutlet weak var cityTextField: UITextField!
#IBOutlet weak var updateWeatherBtn: UIButton!
var netWorkManager = NetworkManager()
override func viewDidLoad() {
super.viewDidLoad()
// netWorkManager.delegate = self
cityTextField.delegate = self
}
#IBAction func closeButtonTapped(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
#IBAction func updateWeatherByCityTapped(_ sender: UIButton) {
//Calling delegate to update the City:
//Dismiss the VC:
dismiss(animated: true, completion: nil)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
cityTextField.endEditing(true)
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
if let city = cityTextField.text {
netWorkManager.fetchWeather(cityName: city)
}
cityTextField.text = ""
}
}
VC1:
import UIKit
import Foundation
import CoreLocation
class WeatherScreen: UIViewController,NetworkManagerDelegate {
//Objects outlets:
#IBOutlet weak var conditionIcon: UIImageView!
#IBOutlet weak var tempLabel: UILabel!
#IBOutlet weak var cityLabel: UILabel!
//TableView Outlet:
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var segeControl: UISegmentedControl!
var models = [DailyWeatherEntry]()
var hourlyModels = [HourlyWeatherEntry]()
var netWorkManager = NetworkManager()
override func viewDidLoad() {
netWorkManager.delegate = self
tableView.register(HourlyTableViewCell.nib(), forCellReuseIdentifier: HourlyTableViewCell.identifier)
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
// Load things once the view will appear
}
#IBAction func locationBtnTapped(_ sender: UIButton) {
//Asking the user for a permission for using his location:
}
func didUpdateWeather(weather: WeatherModel) {
print("Hi")
}
}
extension WeatherScreen: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//creating the cell:
let cell = tableView.dequeueReusableCell(withIdentifier: "weatherCell", for: indexPath) as! HourlyTableViewCell
//Cell Configure:
cell.textLabel!.font = UIFont.systemFont(ofSize: 10)
return cell
}
}
Where is the code for the VC1 ? Without the code for VC 1 it is hard to give an answer. However i'll try to answer as this might be the scenario.
So basically what you are trying to do is make a network call from the VC2 and whatever the response it should be updated in VC1 which is already active somewhere else. Here you just have to set the delegate of the NetworkManger to the VC1 instance. So you have to get the instance of VC1 in VC2.
var netWorkManager = NetworkManager()
//Get this instance in your code
var vc1: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Here you will be setting the delegate
// to VC1 where you will be having the delegate methods
netWorkManager.delegate = vc1
cityTextField.delegate = self
}
While this method works I would not recommend using the network manager in such a way. Try to use completion handlers instead of delegates to get the data and then pass that values between the view controllers.
Edited:
Pass completion like this in the Network Manager performRequest function.
func performRequest(urlString: String, completion: #escaping (Bool, String?, Error?) -> Void) {
guard let url = URL(string: urlString) else {
completion(false, nil, NSError(domain: "URLString is not a valid URL", code: 100, userInfo: nil))
return
}
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
guard let safeData = data, let weather = self.parseJSON(weatherData: safeData) else {
print("Empty data or JSON parse error")
completion(false, nil, error)
}
print("Im not nil")
completion(true, weather, nil)
}
task.resume()
}
And call the api request in the VC2.
#IBAction func updateWeatherByCityTapped(_ sender: UIButton) {
//Calling the api request. Pass your url string here
self. netWorkManager.performRequest(urlString: "") { (success, weather, error) in
guard success else {
print(error as Any)
return
}
// Here you have got the weather data.
// Don't know what is weather model. so simply passing the weather string.
self.delegate.didUpdateWeather(weather: weather)
//Dismiss the VC:
dismiss(animated: true, completion: nil)
}
}
Here adopt the NetworkManagerDelegate to VC1 and before presenting the VC2 set the delegate to VC1. Or if you are not presenting the VC2 from VC1 then use UserNotifications to send the weather data to the VC1.
This line of code var netWorkManager = NetworkManager() creates a new instance of network manager each time it is invoked.
In your case, the network manager in VC1 will not get called when the network manager you created in VC2 receives a network response. They are two separate entities.
There a few things you can think about:
If I understand your scenario correctly, your VC2 is used to get a city name from the user. Does VC2 really need to make a network call? You could restrict VC2 to only fetch the city name.
Make the networkManager a singleton. You can then call it from multiple places in your code. The networkManger can have method to 'fetch' and it can take in a completionHandler (as #Raja Vijaya kumar) had suggested.

Changing Label text on main controller after modal closed swift macOS

I am using delegates to get a string value from my modal. When the modal closes I am trying to update Label text using that string. However, I am getting error: Unexpectedly found nil while implicitly unwrapping an Optional value: file. I am not sure how to fix this. I think it's happening because the view is not yet active.
import Cocoa
class ViewControllerA: NSViewController, SomeDelegate {
#IBOutlet weak var msgLabel: NSTextField!
var s: String = "";
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
func setDetails(s: String) {
self.user = s;
print("Notified", self.s) // <-- prints: Notified hello again
msgLabel.stringValue = self.s <-- DOESN'T WORK
}
func showModal() -> Void {
msgLabel.stringValue = "hello" // <--- WORKS
let cbvc: NSViewController = {
return self.storyboard!.instantiateController(withIdentifier: "ControllerBVC")
as! NSViewController
}()
self.presentAsModalWindow(cbvc);
}
#IBAction func onBtn(_ sender: Any) {
self.showModal();
}
}
protocol SomeDelegate {
func setDetails(s: String)
}
class ViewControllerB: NSViewController {
#IBOutlet weak var textF: NSTextField!
var delegate: SomeDelegate?
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let vc = ViewControllerA()
self.delegate = vc
}
#IBAction func onBtn(_ sender: Any) {
DispatchQueue.main.async {
self.delegate?.setDetails(s: self.textF.stringValue)
self.dismiss("ControllerAVC")
}
}
}
You have a number of problems.
In ViewControllerB.viewDidLoad you are assigning a new instance of ViewControllerA to the delegate property. Don't do that. Your viewDidLoad method should look like this:
override func viewDidLoad() {
super.viewDidLoad()
}
In the showModal method ViewControllerA should assign itself as the delegate on ViewControllerB before ViewControllerB it is presented.
func showModal() -> Void {
let cbvc: NSViewController = {
let vc = self.storyboard!.instantiateController(withIdentifier: "ControllerBVC")
as! ViewControllerB
vc.delegate = self
return vc
}()
self.presentAsModalWindow(cbvc);
}
In the setDetails method just assign the string to your text field directly:
func setDetails(s: String) {
msgLabel.stringValue = s
}

When load WKWebview, its nil

I use Xcode 11.0 and Swift 5.1.
and I use WKWebview for load website, and There's no problem when I first loaded it.
but the second time to load a problem.
filename: TabBarController.swift
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
var isSelected = true
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
let tabBarIndex = tabBar.items?.firstIndex(of: item)
if isSelected == true && tabBarIndex == 0{
let vc = ViewController()
print("Go to amazon page")
vc.handleReload()
}
else{
if tabBarIndex == 0{
isSelected = true
print("home!")
}
else{
isSelected = false
print("setting!")
}
}
}
}
filename: ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController {
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()
let url: URL = URL(string: "https://google.com")!
self.webView.load(URLRequest(url: url))
}
}
//MARK: - function
extension ViewController: WKUIDelegate, WKNavigationDelegate{
func handleReload() {
let url: URL = URL(string: "https://www.amazon.com")!
self.webView.load(URLRequest(url: url))
}
func cacheReset() {
let DataTypes = NSSet(array: [WKWebsiteDataTypeDiskCache, WKWebsiteDataTypeMemoryCache])
let date = NSDate(timeIntervalSince1970: 0)
WKWebsiteDataStore.default().removeData(ofTypes: DataTypes as! Set<String>, modifiedSince: date as Date){
print("Cache Reset End")
}
}
}
The error occurs at self.webView.load(URLRequest(url: url)) in func handleReload() and Here's the error log:
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
I don't understand why WKWebview become nil :( Did I do something wrong?
You don't need to create ViewController again as you have already added as a child to TabBarController. You can grab that instance as below and call the require method on that same instance instead of creating a new.
if isSelected == true && tabBarIndex == 0 {
if let viewController = self.children.first(where: { $0 is ViewController }) as? ViewController {
viewController.handleReload()
}
}

function call from viewmodel to view controller class

I am trying to call one function in ViewController class which was written in view model class.But unfortunately that function is not getting called.I am not getting that where I did mistake.If anyone helps me ,Would be great. Thankyou!
//ViewController Class
import UIKit
class getDetailsViewController: UIViewController {
var getviewmodel: getDetaisViewModel?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func getDetailsAction(_ sender: Any) {
getviewmodel?.getdetailscall()
}
}
//ViewModel Class
import Foundation
import Alamofire
import SwiftyJSON
class getDetaisViewModel{
var url: URL!
func getdetailscall(){
let headers:HTTPHeaders = ["Authorization":"application/json"]
print(headers)
url = URL(string: Constants.apiForGetDetails)
print(url)
let vc = getDetailsViewController.init(nibName: "getDetailsViewController", bundle: nil)
getWebService.requestService(url: url, method: .post, headers: headers, showLoaderFlag: true, viewController: vc, completion: { response in
guard response["code"].int != 503 else {
print("No internet connection")
return
}
guard response != .null else {
return
}
})
}
}
Becuase you haven't initialized the getviewmodel object
Change
var getviewmodel: getDetaisViewModel?
to
var getviewmodel = getDetaisViewModel()
Or initialize getviewmodel object in viewDidLoad method
override func viewDidLoad() {
super.viewDidLoad()
self.getviewmodel = getDetaisViewModel()
}
Change your class name to GetDetaisViewModel

Resources