Error when passing data from one view controller to the other - ios

I'm currently making an rss feed reader in swift 2 and I'm having a hard time trying to pass the url from my tableview to my web view. When I print my url in the tableviewcontroller it adds (optional) in front of the url. However when it has passed into my webviewcontroller it has no (optional) inf front of it. This makes me worry over the fact that something has gone wrong on the way.
tableviewcontroller passing the url to the webviewcontroller:
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath){
let webVC = WebViewController()
webVC.entryUrl = entriesArray[indexPath.row]["link"]
self.navigationController?.pushViewController(webVC, animated: true)
print(entriesArray[indexPath.row]["link"])
}
webviewcontroller receiving the data and trying to use it:
var entryUrl:String!
override func viewDidLoad() {
super.viewDidLoad()
print(entryUrl)
if entryUrl != nil {
print(entryUrl)
let requesturl: NSURL? = NSURL(string: entryUrl)
let request = NSURLRequest(URL: requesturl!)
web.loadRequest(request)
}
loadAddressURL()
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
func loadAddressURL() {
if let requestURL = NSURL(string: entryUrl) {
let request = NSURLRequest(URL: requestURL)
web?.loadRequest(request)
}
}
I get an error on the line:
loadAddressURL()
saying:
EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) on dispatch_semaphore_dispose
From the tableviewcontroller the log prints:
Optional("http://www.google.com/")
But from the webviewcontroller the log just prints:
http://www.google.com/
and after that:
fatal error: unexpectedly found nil while unwrapping an Optional value
Why won't this work and why is the the url not an optional anymore in the webviewcontroller?

Change the line of code to declare object of view Controller as below.
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath){
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let webVC = mainStoryboard.instantiateViewControllerWithIdentifier("WebViewController") as! WebViewController
webVC.entryUrl = entriesArray[indexPath.row]["link"]
self.navigationController?.pushViewController(webVC, animated: true)
print(entriesArray[indexPath.row]["link"])
}
Dont forgot to add indetifier of viewController in storyBoard.

Related

Swift: TableView with initializer returns nil. Trying to dataPass via protocol

rookie here. I created a two tableviews and one gets its data from the previous one. So it has init objects. And I have a view controller where I have a scrollview to display some scrollable images like a catalog. It is just like a menu with categories eventually leading to brands and you tap on the brand so its content comes after.
I do the API call in didSelectRow (for a reason) and use the protocol func. to get images from API to an array then into the scrollview and its subviews. But I can't properly initialize the VC where that tableview that holds the data exists. How can I properly initialize categoriesListVC then use its delegate func?
First VC where I pass data firstly.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let category = data[indexPath.row]
let vc = CategoriesList(brand: category.brand, url: category.url, brandName: category.brandSlugName)
vc?.title = category.category
navigationController?.pushViewController(vc ?? self, animated: true)
}
categoriesList VC with the TableView with init
init?(brand: [String], url: [String], brandName: [String]) {
self.brand = brand
self.url = url
self.brandName = brandName
super.init(nibName: nil, bundle: nil)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
selectedRow2 = indexPath.row
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "CatalogView2") as! ScrollVC2
navigationController?.pushViewController(vc, animated: true)
dataCatalog() //API call - this works I checked. }
And this is how I tried to use its data on the final VC. Would this work? I printed categoriesList here and it gives nil. I probably don't understand something.
var categoriesList: CategoriesList?
override func viewDidLoad() {
super.viewDidLoad()
categoriesList?.delegate = self }
I am trying to use a delegate method in that final VC to use its object to pass data:
func catalogViewModelFromCategories(catalogViewModel: CatalogViewModel) {}
I didn't write the whole code to make it as short as possible. But delegate functions and other things are all in the real code.
In dataCatalog func there is only url session and decoder:
if let url = URL(string: urlString) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) {(data, response, error) in
if error == nil {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(CatalogViewData.self, from: data!)
let catalogFromCategories = CatalogViewModel(images: decodedData.catalogueimages, title: decodedData.cataloguename!)
let catalogViewModel = catalogFromCategories
DispatchQueue.main.async {
self.delegate?.catalogViewModelFromCategories(catalogViewModel: catalogViewModel)
}
} catch {
print(error)
}
return
}
}
task.resume()
}

Passing Information from one view Controller to another

This my main veiw controller code where I populate table veiw with JSON data which I decoded and i have prepare for segue function that i need help with. I want to know to pass title of the movie and overview to next view controller:
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var name = [String]()
var dis = [String]()
let urls = "https://api.themoviedb.org/3/movie/top_rated?api_key=964086a2711d5d6f3fa828013fd5c3b0&language=en-US&page=1"
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(UINib(nibName: "Mov", bundle: nil), forCellReuseIdentifier: "hello")
session()
// Do any additional setup after loading the view.
}
func session(){
let session = URLSession(configuration: .default, delegate: nil, delegateQueue: .main)
let url = URL(string: urls)!
let task = session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if (error != nil){
print(error!)
return
}
if let safeData = data{
self.parseJSON(movieData:safeData)
}
})
task.resume()
}
func parseJSON (movieData :Data){
let decoder = JSONDecoder()
do{
let decodeData = try decoder.decode(MovieData.self, from: movieData)
for movie in decodeData.results {
self.name.append(movie.title)
self.dis.append(movie.overview)
self.tableView.reloadData()
//print(movie.overview)
self.tableView.reloadData()
}
// print("\(self.name)")
//print(self.name.count)
}catch{
print(error)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let des = segue.destination as! DetViewController
}
}
extension FirstViewController:UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//print(name.count)
return name.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "hello", for: indexPath) as! Mov
cell.topLabel.text = self.name[indexPath.row]
cell.bottomLabel.text=self.dis[indexPath.row]
return cell
}
}
extension FirstViewController:UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "dhruv", sender: self)
}
}
This code below is the struct that i am using to decode my JSON data :
import UIKit
struct MovieData:Decodable {
var results : [Result]
}
struct Result:Decodable {
var title : String
var overview:String
}
And lastly I have my destination veiw controller which were I am tryong to pass my information too such as movie title and overview;
import UIKit
class DetViewController: UIViewController {
var movie : MovieData
override func viewDidLoad() {
super.viewDidLoad()
print(movie)
// Do any additional setup after loading the view.
}
}
So if would help i would appreciate it. The main purpiose for this is that at end if someone click on the cell with name of the movie i want to display the name and overveiw of the movie in to the screen . I am able to get to new view Controller when i press on one of the cell in the table view i just figure how to pass the value.
You need to declare a variable of type MovieData to hold your decoded data. Let's call it movieData and make sure you declare it at the top of your ViewController:
var movieData: MovieData?
Inside of your parseJSON() function, you want to assign the decoded data to your movieData variable.
movieData = decodeData
According to your code and your question, I'm pretty sure you are trying to pass the info about the movie selected and not the whole results array. So, if that's the case, inside DetViewController, change the type of movie to Result, since you are only interested in a specific movie.
var movie: Result
In prepareForSegue, assign the value of the selected movie to your DetViewController's movie property (and that's how you pass data to your next ViewController):
if let indexPath = tableView.indexPathForSelectedRow {
des.movie = movieData.results[indexPath.row]
}
Now inside your DetViewController, you can access title and overview as follows: movie.title and movie.overview
A piece of advice:
Instead of naming your structs MovieData and Result, consider naming them MovieArray and Movie. Instead of naming your MovieData property results, consider naming it movies.

Reloading a WebView with a new url from another ViewController

I'm having trouble reloading a UIWebiView from another view controller. When I try to send the new url and reload it, the app crashes due to the webview being nil.
I made a simple test app to narrow down my problem, right now its a simple page with a webivew showing google and a button on the view. When the button is pressed, it modally presents another page. This page only has one button that when pressed, it dismissed itself and calls a function in the first view asking to refresh it with a new url. Heres my code:
class ViewController: UIViewController {
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlRequest = NSURLRequest(URL: NSURL(string: "https://www.google.com")!)
webView.loadRequest(urlRequest)
}
#IBAction func showViewPressed(sender: AnyObject) {
let showView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("showView")
self.presentViewController(showView, animated: true, completion: nil)
}
func reloadWebView(newURL: String) {
let urlRequest = NSURLRequest(URL: NSURL(string: newURL)!)
webView.loadRequest(urlRequest)
}
}
and for the second view:
class ReloadViewController: UIViewController {
#IBAction func buttonPressed(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: {
ViewController().reloadWebView("https://www.yahoo.com")
})
}
}
it crashes with fatal error: unexpectedly found nil while unwrapping an Optional value
Alright, so it seems before I ask a question I spend hours trying to find a solution to no avail. Then 5 minutes after I post a question thinking i'll never find the solution... I find it. So I found a solution that was for a tableview here this also happened to apply to webviews. So for anyone in the future looking for the same thing, heres what I did:
class ViewController: UIViewController {
#IBOutlet var webView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let urlRequest = NSURLRequest(URL: NSURL(string: "https://www.google.com")!)
webView.loadRequest(urlRequest)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(reloadWebView), name:"reload", object: nil)
}
#IBAction func showViewPressed(sender: AnyObject) {
let showView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("showView")
self.presentViewController(showView, animated: true, completion: nil)
}
func reloadWebView() {
let urlRequest = NSURLRequest(URL: NSURL(string: "https://yahoo.com")!)
webView.loadRequest(urlRequest)
}
}
and for the second view:
class ReloadViewController: UIViewController {
#IBAction func buttonPressed(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: {
NSNotificationCenter.defaultCenter().postNotificationName("reload", object: nil)
})
}
}
and it worked beautifully!

webview turns nil after calling function from another controller

Hello if anybody can help me thanks!
I got two viewcontrollers HomeController and TableController
In HomeController i got a webview and when i go to the TableController and call a function to load an another request for the webview. The webview returns nil.
The HomeController is the main screen of the application which is a webview. The TableController is a table from a hamburger menu left of the application. When i open it and click on an item, I want to get the url of that item and use that url to send a request with the same HomeController. But at the moment when i reach connectUrl() i get an fatal error and it is because the webView is nil.
edit: answer below
Code below:
TableController
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var HomeViewController: HomeController {
return self.storyboard?.instantiateViewControllerWithIdentifier("homeController") as! HomeController
}
let url = self.item[indexPath.item].url
HomeViewController.connectUrl(url!)
}
HomeController
class HomeController: UIViewController{
static let sharedInstance = HomeController()
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
webView = WKWebView(frame: view.bounds, configuration: configuration)
self.view.addSubview(self.webView)
}
func connectUrl(url: String){
let url2 = NSURL(string: url)
let req = NSURLRequest(URL: url2!)
print(req)
self.webView!.loadRequest(req)
}}
Is there any domain erron in console?/
if yes
add it to plist.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
I found the solution after trial and error with many development options like delegates, segue, instantiateViewController etc.
In the end NSNotificationCenter was the solution for me.
I created this function in my TableController to send a notification to the HomeController to call ConnectUrl.
func sendData(url: String){
self.dismissViewControllerAnimated(true, completion: nil)
let itemDictionary = ["itemUrl": url]
NSNotificationCenter.defaultCenter().postNotificationName("connectUrl", object: nil, userInfo: itemDictionary)
}
}
}
In the HomeController I added an Observer to receive the noticatification and unpack the dictionary is received to get the url.
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: view.bounds, configuration: configuration)
self.view.addSubview(self.webView!)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "connectUrl:", name: "connectUrl", object: nil)
}
func connectWithSpecifiedItem(notification: NSNotification){
let itemUrl = notification.userInfo!["itemUrl"] as! String
let url2 = NSURL(string: itemUrl)
let req = NSURLRequest(URL: url2!)
self.webView!.loadRequest(req)
}
This is not a WKWebView related issue. The actual problem here is that you don't know how to return data from a view controller.
The most basic solution for this is a delegate. First, create a TableControllerDelegate.
protocol TableControllerDelegate {
func tableController(tableController: TableController, didSelectItemWithURL: NSURL)
}
Then you implement this in your HomeController:
class HomeController: UIViewController, TableControllerDelegate {
func tableController(tableController: TableController, didSelectItemWithURL url: NSURL) {
webView.loadRequest(NSURLRequest(URL: url))
}
}
In your TableController, things get a little simpler and less connected. You let it have a TableControllerDelegate field and simply call that to pass the selected item URL back to whatever view opened it.
Note that there is no direct relationship between TableController and HomeController anymore. This is a good practice.
class TableController: UITableViewController {
weak delegate: TableControllerDelegate?
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let url = self.item[indexPath.item].url
delegate?.tableController(self, didSelectItemWithURL: url)
}
}
Now when you present your TableController from your HomeController you simply configure its delegate:
func displayTableController() {
let tableController = storyboard?.instantiateViewControllerWithIdentifier("tableController") as! TableControllerController
tableController.delegate = self
}
More code, but this is a very common pattern in iOS.

Swift Cancelling the ActivityViewController causes fatal error

Okay so I got my ActivityViewController to work fine when it is sharing like normal however whenever a user hits cancel after they invoked the ActivityViewController , I get the annoying fatal error.
fatal error: unexpectedly found nil while unwrapping an Optional value
So it looks like I did not unwrap my optionals well. Here's the share method :
#IBAction func share(sender: UIBarButtonItem) {
var memeedimage = generateMemedImage()
let activityViewController = UIActivityViewController(activityItems:[memeedimage] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = {
(activity, success, returneditems, error) in
println("Activity: \(activity) Success: \(success) Items: \(returneditems) Error: \(error)")
self.save()
activityViewController.dismissViewControllerAnimated(true, completion:{
let memevc:MemeTableViewController = MemeTableViewController()
self.presentViewController(memevc, animated: true, completion: nil)
})
}
and the share method calls the save function which generates the object called meme using an implicitly unwrapped optional which causes the error :
func save(){
var meme : MemeObject!
meme = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
So I decided to safely unwrap the meme optional value but that invoked another problem
func save(){
var meme : MemeObject?
if let memez = meme{
meme = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
else{
println("Optionals man")
}
}
Now when the the object is not nil , "Optionals man" is printed which shouldn't happen and the completionwithitemshandler property in the share method didn't present the table view controller which should happen directly after the user shares the object.
Code for MemeTableViewController :
import UIKit
class MemeTableViewController : UIViewController,UITableViewDelegate,UITableViewDataSource
{
var memesz: [MemeObject]!
#IBOutlet var tableView: UITableView!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
memesz = appDelegate.memes
//tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.hidesBarsOnSwipe = true
navigationController?.hidesBarsOnTap = true
}
//reserves the number of rows needed to display the image
func tableView(tableView : UITableView, numberOfRowsInSection section : Int)->Int
{
return memesz.count
}
//Reserves the row to be dequeued for display
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewzCell = tableView.dequeueReusableCellWithIdentifier("MemesCell", forIndexPath: indexPath) as! TableViewzCell
let memezrow = memesz[indexPath.row]
cell.label1.text = memezrow.textFieldtop
cell.label2.text = memezrow.textFieldbottom
cell.imageview.image = memezrow.memedImage
return cell
}
//Method to do something when the row is selected
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let detailController = self.storyboard!.instantiateViewControllerWithIdentifier("FullScreenMeme") as! FullScreenMeme
detailController.meme = memesz[indexPath.row]
self.navigationController!.pushViewController(detailController, animated: true)
}
}
Any help please?
After looking your code, it seems like you haven't initialised the variable 'meme'.
func save(){
var meme : MemeObject? = MemeObject(textFieldtop : texfieldtop.text! ,textFieldbottom : textfieldbottom.text! ,image : imagePickerView.image! , memedImage : generateMemedImage())
if let memez = meme{
(UIApplication.sharedApplication().delegate as! AppDelegate).memes.append(meme!)
}
else{
println("Optionals man")
}
}
}
#IBAction func share(sender: UIBarButtonItem) {
var memeedimage = generateMemedImage()
let activityViewController = UIActivityViewController(activityItems:[memeedimage] , applicationActivities: nil)
presentViewController(activityViewController, animated: true, completion: nil)
activityViewController.completionWithItemsHandler = {
(activity, success, returneditems, error) in
println("Activity: \(activity) Success: \(success) Items: \(returneditems) Error: \(error)")
self.save()
let memevc = self.storyboard!.instantiateViewControllerWithIdentifier("MemeTableViewConroller") as! MemeTableViewController
self.presentViewController(memevc, animated: true, completion: nil)
}
}

Resources