I am trying to transfer a JSON value to a label in another view controller, from pressing a button in my Main View Controller. My first view controller is called 'ViewController' and my second one is called 'AdvancedViewController. The code below shows how I get the JSON data, and it works fine, displays the JSON values in labels in my MainViewController, but when I go to send a JSON value to a label in my AdvancedViewController, I press the button, it loads the AdvancedViewController but the label value is not changed? I have assigned the label in my AdvancedViewController and I'm not sure why its not working. I am trying to transfer it to the value 'avc.Label' which is in my advanced view controller
The main label code shows how I get it to work in my MainViewController
Code below:
My Main ViewController:
guard let APIUrl = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=" + text + "&appid=e7b2054dc37b1f464d912c00dd309595&units=Metric") else { return }
//API KEY
URLSession.shared.dataTask(with: APIUrl) { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
//Decoder
do {
let weatherData = try decoder.decode(MyWeather.self, from: data)
if (self.MainLabel != nil)
{
if let gmain = (weatherData.weather?.first?.main) { //using .first because Weather is stored in an array
print(gmain)
DispatchQueue.main.async {
self.MainLabel.text! = String (gmain)
}
}
}
let avc = AdvancedViewController(nibName: "AdvancedViewController", bundle: nil)
if (avc.Label != nil)
{
if let mTest = (weatherData.weather?.first?.myDescription) { //using .first because Weather is stored in an array
DispatchQueue.main.async {
avc.Label.text! = String (mTest)
}
}
}
In AdvancedViewController create variable that store the value of mTest
class AdvancedViewController: ViewController {
var test: String!
override func viewDidLoad(){
super.viewDidLoad()
if let test = test {
myLabel.text = test
}
}
}
let vc = storyboard?.instantiateViewController(withIdentifier: "AdvancedViewController") as! AdvancedViewController
if let mTest = (weatherData.weather?.first?.myDescription) {
vc.test = mTest
}
DispatchQueue.main.async {
present(vc, animated: true, completion: nil)
}
You shouldn't try to manipulate another view controller's views. That violates the OO principle of encapsulation. (And it sometimes just plain doesn't work, as in your case.)
Salman told you what to do to fix it. Add a String property to the other view controller, and then in that view controller's viewWillAppear, install the string value into the desired label (or do whatever is appropriate with the information.)
Related
I am having problems assigning the stringValue from qrcode scanner to a variable on another class.
Scan the QR code
the string from QR code need's to be assigned to id variable of the other class
perform a segue to that class's viewController
So i created a init on the other class where the variable id is and assigned it to temp as you can see but still the temp is not being assigned to the id variable and when i execute the code it gets error nil to the variable id...?
Class: QRScannerController
if metadataObj.stringValue != nil {
let destVC = MonitorimiViewController(temp: metadataObj.stringValue!)
destVC.id = metadataObj.stringValue!
do {
guard let tabBarController = self.storyboard?.instantiateViewController(withIdentifier: "tabBarController") as? UITabBarController else {
return
}
(tabBarController.viewControllers![0] as? MonitorimiViewController)?.id = metadataObj.stringValue!
tabBarController.modalTransitionStyle = .crossDissolve
tabBarController.modalPresentationStyle = .custom
self.present(tabBarController, animated: true, completion: nil)
}
}
captureSession.stopRunning()
}
Class: MonitorimiViewController
init(temp : String){
id = temp
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
var id:String?
func fetchAndReloadData(){
APICaller.shared.getVehicles(for: id!) {[weak self] (result) in // error nil value at id!
guard let self = self else { return }
switch result {
case .success(let vehicle):
self.listOfVechicle = vehicle
DispatchQueue.main.async {
self.monitorimiTableView.reloadData()
}
case .failure(let error):
print(error)
}
}
}
For any clarifications please feel free to ask because I have been stuck with this problem for days :(
If you can't debug yourself (experience will be earned later), never write this kind of code:
(tabBarController.viewControllers![0] as? MonitorimiViewController)?.id = metadataObj.stringValue!
So, what does it says:
tabBarController.viewControllers!: If there is no viewControllers: crash.
(... as? SomeClass): If it's not of that class (or compliant to the protocol, or a subclass, etc.), the cast will fail, and it will be nil.
So in your case, it's:
(someNilValue)?.id = someId
So, nothing gets someId.
How to know that by yourself:
guard let viewcontrollers = tabBarController.viewControllers else {
print("tabBarController.viewControllers is nil")
return
}
guard let firstVC = viewControllers.first else {
print("viewControllers is empty")
return
}
guard let asMonitorimiVC = firstVC as? MonitorimiViewController else {
print("firstVC is NOT a MonitorimiViewController instance: \(firstVC)") //by default, you should see its type in console, by you could also use type(of:)
return
}
asMonitorimiVC.id = someId
Yours should then fail at guard let asMonitorimiVC, because firstVC is a UINavigationController.
guard let asNavigationVC = firstVC as? UINavigationController else {
print("firstVC is NOT a UINavigationController instance: \(firstVC)") //by default, you should see its type in console, by you could also use type(of:)
return
}
guard let firtVCFromNavVC = asNavigationVC.viewcontrollers.first else {
print("asNavigationVC.viewcontrollers is empty") //But that shouldn't be the case, I used viewcontrollers, but you could also use topViewController in this case
return
}
guard let asMonitorimiVC = firtVCFromNavVC as? MonitorimiViewController else {
print("firtVCFromNavVC is NOT a MonitorimiViewController instance: \(firstVC)") //by default, you should see its type in console, by you could also use type(of:)
return
}
asMonitorimiVC.id = someId
Of course, that's a verbose solution, you might want to cut it a little, but when debugging, do not hesitate to make explicit code:
guard let navVC = tabBarController.viewControllers?.first as? UINavigationController else {
print("tabBarController.viewControllers is nil or empty, or the first of it is not a UINavigationController")
return
}
guard let asMonitorimiVC = navVC.viewControllers.first as? MonitorimiViewController else {
print("navVC.viewControllers is empty, or its first is not a is NOT a MonitorimiViewController instance: \(navVC.viewControllers.first)")
return
}
Side note: You might notice, that if you had answered correctly my first question in comment in your previous question, we would have spotted directly the issue:
Is (tabBarController.viewControllers![0] as? MonitorimiViewController) nil?
Because print("Is it nil: (tabBarController.viewControllers![0] as? MonitorimiViewController))` should show that's it's in fact nil, since the cast fails...
I am trying to create ButtonBar based paging menu but it will be dynamic because the data (title for no. of menu) created will come from the server. Unfortunately, there is no such example created for this situation. I know it's really stupid to request such example because I have no idea for this case. I just want to use this library as new because it seems more smooth then other libraries that I used. So, I really need help with an example project is appreciated. And I want to use the same view controller for each different tab like UITableViewController for showing data for each tab.
Any Help with that??? An example will be appreciated...Thanks..
My solution is to make a synchronous call to the server to fetch the data that i need to populate the menu. It is possible that there is an asynchronous solution but i can't quite figure it out, since my app depends on completely on the menu items.
fileprivate func parseMenuItems() {
self.menuItems = [MenuObject]()
let url = URL(string: MENU_URL)
do {
let data = try Data(contentsOf: url!)
let json = JSON(data: data)
for (_, subJson) in json["items"] {
guard let name = subJson["name"].string else {return}
guard let url = subJson["url"].string else {return}
let menuItem = MenuObject(name: name, url: url)
self.menuItems.append(menuItem)
}
} catch {
print(error)
}
}
For parsing I am using SwiftyJson, but that is irrelevant here.
This function (parseMenuItems()) is called before super.viewDidLoad().
Next step is to populate view controllers with menuItems data:
override func viewControllers(for pagerTabStripController: PagerTabStripViewController) -> [UIViewController] {
var children = [UIViewController]()
for menuItem in menuItems! {
let child = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "TestViewController") as! TestViewController
child.name = menuItem.name
child.url = menuItem.url
children.append(child)
}
}
return children
}
Hope it helps :)
This is my table created in Firebase here. I have a search button. The button action will be at first it will fetch data from firebase and then it will send it to another view controller and will show it in a table view. but main problem is that before fetching all data from Firebase
self.performSegueWithIdentifier("SearchResultPage", sender: self )
triggers and my next view controller shows a empty table view. Here was my effort here.
From this post here I think that my code is not placed well.
Write this line of code in your dataTransfer method in if block after complete for loop
self.performSegueWithIdentifier("SearchResultPage", sender: self )
Try sending the search string as the sender when you are performing the segue, for example:
self.performSegueWithIdentifier("SearchResultPage", sender: "searchString")
Then, in your prepare for segue get the destination view controller and send over the string to the new view controller. something like:
destinationViewController.searchString = sender as! String
When the segue is completed and you have come to the new view controller, you should now have a searchString value that is set, use this value to perform your query and get the relevant data to show in the new table view.
Please note that this is just one solution of many, there are other ways to achieve this.
The reason why you are not able to send data is because, you are trying to send data even before the data could actually be fetched.
Try the following where you are pushing to next view controller only after getting the data
func dataTransfer() {
let BASE_URL_HotelLocation = "https://**************.firebaseio.com/Niloy"
let ref = FIRDatabase.database().referenceFromURL(BASE_URL_HotelLocation)
ref.queryOrderedByChild("location").queryStartingAtValue("uttara").observeEventType(.Value, withBlock: { snapshot in
if let result = snapshot.children.allObjects as? [FIRDataSnapshot] {
for child in result {
let downloadURL = child.value!["image"] as! String;
self.storage.referenceForURL(downloadURL).dataWithMaxSize(25 * 1024 * 1024, completion: { (data, error) -> Void in
let downloadImage = UIImage(data: data!)
let h = Hotel()
h.name = child.value!["name"] as! String
print("Object \(count) : ",h.name)
h.deal = child.value!["deal"] as! String
h.description = child.value!["description"] as! String
h.distance = child.value!["distance"] as! String
h.latestBooking = child.value!["latestBooking"] as! String
h.location = child.value!["location"] as! String
h.image = downloadImage!
self.HotelObjectArray.append(h)
})
}
let storyboard = UIStoryboard(name:"yourStoryboardName", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("SearchResultPageIdentifier") as! SearchResultPage
self.navigationController.pushViewController(vc, animated: true)
}
else {
print("no results")
}
})
}
I'm trying to refactor a function to have it globally available. At the end of the function, a UITableView needs to be reloaded. I was thinking about passing the VC, but I still get
Value of type 'UIViewController' has no member 'tableView'
I know that I get that error because tableView is not part of a UIViewController by default, but how do I bypass it?
func downloadAvatar(_ userForAvatar: String, vc: UIViewController) {
let whereClause = "objectId = '\(userForAvatar)'"
let dataQuery = BackendlessDataQuery()
dataQuery.whereClause = whereClause
let dataStore = backendless?.persistenceService.of(BackendlessUser.ofClass())
dataStore?.find(dataQuery, response: { (users : BackendlessCollection?) in
let withUser = users?.data.first as! BackendlessUser
let imgURL = withUser.getProperty("Avatar") as? String
avatare.setValue(imgURL, forKey: userForAvatar)
vc.tableView.reloadData() // problem here
}) { (fault : Fault?) in
print("error: \(fault)")
}
}
Help is very appreciated.
Instead of have having vc: UIViewController as an argument, you should just have tv: UITableView instead. Then instead of passing the view controller, you pass it's tableView. Although if this isn't working, you could try a completion block instead.
func downloadAvatar(_ userForAvatar: String, completion: #escaping () -> ()) {
let whereClause = "objectId = '\(userForAvatar)'"
let dataQuery = BackendlessDataQuery()
dataQuery.whereClause = whereClause
let dataStore = backendless?.persistenceService.of(BackendlessUser.ofClass())
dataStore?.find(dataQuery, response: { (users : BackendlessCollection?) in
let withUser = users?.data.first as! BackendlessUser
let imgURL = withUser.getProperty("Avatar") as? String
avatare.setValue(imgURL, forKey: userForAvatar)
completion()
}) { (fault : Fault?) in
print("error: \(fault)")
}
}
Then assuming you're calling this method from the respective view controller, you can reload the tableView data within the completion block.
downloadAvatar("$uid") { [weak self] in
self?.tableView.reloadData()
}
'UIViewController' has no member 'tableView'
It tells everything ! Your vcis of type UIViewController , obviously it has no member named tableView.
You have used the parameter vc only once and its for reloading table. So instead of passing it, try passing the tableveiew reference itself.
Because of some reasons, if you cant do this and you should use ViewController itself, try create a protocol, implement it in all of your VCs and use that as type. tableview should be a member of that protocol
This is basic programming. Hope this helps
In my application I have a modal view that can be called from two different viewControllers. Let’s call them MainViewController and DetailViewController. This modal view is also embedded in a UINavigationController.
I’m trying to do an if/else statement based off of which ViewController triggered the modal to appear.
The code that I currently have in the modal's ViewController is:
if presentingViewController is DetailTableViewController {
//Update a current Distance
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
distance.name = name!
distance.length = length!
if let distanceImage = distanceImageView.image {
distance.image = UIImagePNGRepresentation(distanceImage)
}
do {
try managedObjectContext.save()
} catch {
print(error)
return
}
}
} else if presentingViewController is ViewController {
//Save a new Distance
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
distance = NSEntityDescription.insertNewObjectForEntityForName("Distance", inManagedObjectContext: managedObjectContext) as! Distance
distance.name = name!
distance.length = length!
if let distanceImage = distanceImageView.image {
distance.image = UIImagePNGRepresentation(distanceImage)
}
do {
try managedObjectContext.save()
} catch {
print(error)
return
}
}
}
It appears though that the result of presentingViewController is only returning the UINavigationController that it’s embedded in. Is there some way that I can get around that controller and test against the view that segues to the UINavigationController in the first place?
I'm working with iOS 9 and Swift 2. Thanks in advance for any help!
I guess what you are presenting is navigationcontroller. So one can test for navigationcontrollers property i.e viewControllers which will return an array of view controllers i.e either MainViewController and DetailViewController. So here one can use filter operation for array to check the controller one wants to test.As shown below.
let controllerList = (presentingViewController as? UINavigationController).viewControllers
let isControllerExsist = controllerList.filter{$0 is MainViewController}
if isControllerExsist.count>0
{
print("isMainViewCntroller")
}
if it's between specifically only two view controllers you can pass a Bool to the destination view controller.
In your firstVC:
let destinationVC = DestinationVC()
// or storyboard.instantiateViewControllerWithIdentifier("destinationVC") as! DestinationVC
destinationVC.isDetail = true
showViewController(destinationVC)
In your destinationVC, in your -viewDidLoad:
if isDetail {
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
distance.name = name!
distance.length = length!
if let distanceImage = distanceImageView.image {
distance.image = UIImagePNGRepresentation(distanceImage)
}
do {
try managedObjectContext.save()
} catch {
print(error)
return
}
}
} else {
//Save a new Distance
if let managedObjectContext = (UIApplication.sharedApplication().delegate as? AppDelegate)?.managedObjectContext {
distance = NSEntityDescription.insertNewObjectForEntityForName("Distance", inManagedObjectContext: managedObjectContext) as! Distance
distance.name = name!
distance.length = length!
if let distanceImage = distanceImageView.image {
distance.image = UIImagePNGRepresentation(distanceImage)
}
do {
try managedObjectContext.save()
} catch {
print(error)
return
}
}
}
and in your secondVC set the bool isDetail to false
I would recommend using prepareForSegue and populate a field on that view controller you're segueing to (make it a weak var!). If the view controller you're segueing to is the navigation controller, which it probably is, you'll need to make a custom navigation controller with the var, and then the presentingViewController will be this custom type, and will have the presenting view controller. Either that or in the navigation controller's prepareForSegue populate another var on your modal view.