Swift 3 / How to call tableView.reloadData() in global function? - ios

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

Related

Casting to specific type from a generic type

I want to cast to a specific class how can I achieve this?
public class func parseResponse<T:Decodable>(className:T.Type, response:[String: AnyObject],myid:String,index : Int)->(Bool,T?)
{
guard let data = try? JSONSerialization.data(withJSONObject: response) else {
return (false,nil)
}
if myid.count > 0 {
// let clas1 = NSClassFromString(String(describing: T.Type.self))!
let obj = T.self as! BaseResponse
print(String(describing: T.self))
obj.setKeys(myid: myid, index: index)
}
let decoder = JSONDecoder()
do {
let object = try decoder.decode(T.self, from: data)
return (true,object)
}
catch {
return (false,nil)
}
}
I have this method where it is crashing at let obj = T.self as! BaseResponse this line saying that it cannot be type cast.
The statement let obj = T.self as! BaseResponse doesn't make much sense. You're trying to create an object by casting a class into another class, when you should be trying to cast an instance of a class into another class.
In order for this algorithm to work, you need T to be both Decodable and a subtype of BaseResponse. Since that's the case, you should say so in your signature.
public class func parseResponse<T>(...) -> (Bool,T?)
where T: BaseResponse & Decodable
With that, there's no need to cast anything. Your code hints that BaseResponse has a class-level method setKeys, so you can just call it on T (which you now know to be a subtype of BaseResponse):
T.setKeys(...)
On an unrelated note (Bool, T?) is a very strange return type for this usage. The optionality of T already provides everything the Bool is doing, much more cleanly.

Cannot transfer JSON value to Label in SecondViewController

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.)

Returning data from function in Firebase observer code block swift

I'm new to firebase and I want to know if is any possible way to return data in observer block. I have class ApiManager:NSObject and in this class I want to create all my firebase function that will return some kind of data from database. This is one of my function in this class
func downloadDailyQuote() -> [String:String] {
let reference = Database.database().reference().child("daily")
reference.observeSingleEvent(of: .value) { (snap) in
return snap.value as! [String:String] //I want to return this
}
return ["":""] //I don't want to return this
}
And if I now do something like let value = ApiManager().downloadDailyQuote(), value contains empty dictionary. Is any solution for that?
Update: When you call .observeSingleEvent, you call the method asynchronously. This means that the method will start working, but the response will come later and will not block the main thread. You invoke this method, but there is no data yet and therefore you return an empty dictionary.
If you use the completion block, then you will get the data as soon as the method action is completed.
func downloadDailyQuote(completion: #escaping ([String:String]) -> Void) {
let reference = Database.database().reference().child("daily")
reference.observeSingleEvent(of: .value) { (snap) in
if let dictionaryWithData = snap.value as? [String:String] {
completion(dictionaryWithData)
} else {
completion(["" : ""])
}
}
}

Remove the observer using the handle in Firebase in Swift

I have the following case. The root controller is UITabViewController. There is a ProfileViewController, in it I make an observer that users started to be friends (and then the screen functions change). ProfileViewController can be opened with 4 tabs out of 5, and so the current user can open the screen with the same user in four places. In previous versions, when ProfileViewController opened in one place, I deleted the observer in deinit and did the deletion just by ref.removeAllObservers(), now when the user case is such, I started using handle and delete observer in viewDidDisappear. I would like to demonstrate the code to find out whether it can be improved and whether I'm doing it right in this situation.
I call this function in viewWillAppear
fileprivate func firObserve(_ isObserve: Bool) {
guard let _user = user else { return }
FIRFriendsDatabaseManager.shared.observeSpecificUserFriendshipStart(observer: self, isObserve: isObserve, userID: _user.id, success: { [weak self] (friendModel) in
}) { (error) in
}
}
This is in the FIRFriendsDatabaseManager
fileprivate var observeSpecificUserFriendshipStartDict = [AnyHashable : UInt]()
func observeSpecificUserFriendshipStart(observer: Any, isObserve: Bool, userID: String, success: ((_ friendModel: FriendModel) -> Void)?, fail: ((_ error: Error) -> Void)?) {
let realmManager = RealmManager()
guard let currentUserID = realmManager.getCurrentUser()?.id else { return }
DispatchQueue.global(qos: .background).async {
let specificUserFriendRef = Database.database().reference().child(MainGateways.friends.description).child(currentUserID).child(SubGateways.userFriends.description).queryOrdered(byChild: "friendID").queryEqual(toValue: userID)
if !isObserve {
guard let observerHashable = observer as? AnyHashable else { return }
if let handle = self.observeSpecificUserFriendshipStartDict[observerHashable] {
self.observeSpecificUserFriendshipStartDict[observerHashable] = nil
specificUserFriendRef.removeObserver(withHandle: handle)
debugPrint("removed handle", handle)
}
return
}
var handle: UInt = 0
handle = specificUserFriendRef.observe(.childAdded, with: { (snapshot) in
if snapshot.value is NSNull {
return
}
guard let dict = snapshot.value as? [String : Any] else { return }
guard let friendModel = Mapper<FriendModel>().map(JSON: dict) else { return }
if friendModel.friendID == userID {
success?(friendModel)
}
}, withCancel: { (error) in
fail?(error)
})
guard let observerHashable = observer as? AnyHashable else { return }
self.observeSpecificUserFriendshipStartDict[observerHashable] = handle
}
}
Concerning your implementation of maintaining a reference to each viewController, I would consider moving the logic to an extension of the viewController itself.
And if you'd like to avoid calling ref.removeAllObservers() like you were previously, and assuming that there is just one of these listeners per viewController. I'd make the listener ref a variable on the view controller.
This way everything is contained to just the viewController. It also is potentially a good candidate for creating a protocol if other types of viewControllers will be doing similar types of management of listeners.

Why is the elements being replaced instead of appended?

I am reading a very large json file and pulling in data. I can currently pull in the correct data, but when I try and append it to an array, it ends up replacing it.
func parseJSON(json: JSON){
let predicate = {
(json: JSON) -> Bool in
if let jsonID = json["class"].string where jsonID == "sg-header-heading"{
return true
}
return false
}
var backclass = ViewController()
let foundJSON = json.findmultiple(backclass, predicate: predicate)
Using this extension to search for multiple values that match "sg-header-heading"
When I print the array in the extension this is what I get. But when I print it above I get nil. Also I am only getting one value per instead of 6 values in the end.
extension JSON{
func findmultiple(viewclass: ViewController, #noescape predicate: JSON -> Bool) -> JSON? {
var backclass = ViewController()
if predicate(self) {
return self
}
else {
if let subJSON = (dictionary?.map { $0.1 } ?? array) {
for json in subJSON {
if let foundJSON = json.findmultiple(backclass, predicate: predicate) {
let shorten = foundJSON["html"].stringValue
let firstshort = shorten.componentsSeparatedByString(" ")
let secondshort = firstshort[1].componentsSeparatedByString("\r")
let classname = secondshort[0]
if(classname == "STUDY HALL (INSTRUCT)"){
print("skip")
}else{
backclass.importClass.append(classname)
print("fsgsfg \(backclass.importClass)")
//backclass.collection.reloadData()
}
}
}
}
}
return nil
}
}
You are first calling findmultiple() from the outermost context (let foundJSON = json.findmultiple(predicate)). As the code is executed and findmultiple() is actually executed it creates a new instance of with var backclass = ViewController(). As the code goes into its loop it executes findmultiple() again, recursively. As the code goes into its loop an additional time it creates a new instance of backclass. Each time it goes through the loop, the recursion goes deeper and a new instance of ViewController is created each time so backclass always points to a new instance of ViewController.
The solution to this would be to create backclass at a higher level. You might try an approach like this:
var backclass = ViewController()
let foundJSON = json.findmultiple(backclass, predicate)
Note that this would mean passing backclass to findmultiple from inside the loop where the recursive call happens.

Resources