How to resolve Escaping closure captures 'inout' parameter 'data' - ios

I have the following function in a separate swift file which I use to make Firebase calls for data:
func fetchPosts(data: inout [Post]) {
postRef.observe(DataEventType.value) { (snapshot) in
data.removeAll() // ***Error thrown here***
if snapshot.value is NSNull {
} else {
var counter: UInt = 0
for item in snapshot.children {
let userID = (item as! DataSnapshot).key
self.postRef.child(userID).observe(DataEventType.value) { (snap) in
counter = counter + 1
for child in snap.children {
if let snapshot = child as? DataSnapshot,
let post = Post(snapshot: snapshot) {
data.append(post) // ***Error thrown here***
}
if (counter == snapshot.childrenCount) {
data = data.sorted(by: { $0.id > $1.id }) // ***Error thrown here***
}
}
}
}
}
}
}
In my view I have the following:
#State var posts: [Post] = [] // which is the place holder for the posts data
and then i have the following call
func fetchPosts() {
postStore.fetchPosts(data: &posts)
}
Which calls the function above and passes the [Post] array by reference
My issue is that I get the following error Escaping closure captures 'inout' parameter 'data' in the above function and I can not figure out what I need to do to resolve it!?
Has anyone ever encountered this error and what do you do to resolve it!?

That won't work with #State, instead you have to use completion callback pattern, like
func fetchPosts() {
self.posts = [] // reset
postStore.fetchPosts() { newPosts in
DispatchQueue.main.async {
self.posts = newPosts
}
}
}
and fetching function
func fetchPosts(completion: #escaping ([Post]) -> () ) {
// ...
var data: [Post] = [] // data is a local variable
// ...
if (counter == snapshot.childrenCount) {
completion(data.sorted(by: { $0.id > $1.id }))
}
// ...
}

Related

How to combine two foreach to one in Swift?

I have one function which is having some logic which have 2 foreach loop but i want to make code compact so I am trying to use compactmap
func getData() -> [String] {
var ids = [String]()
self.item?.connections?.forEach { connection in
connection.validLine?.forEach { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
ids += checkinGroups.connections?.compactMap { $0.id } ?? []
}
}
}
return ids
}
so instead of 2 foreach i am trying to make in one by using self.item?.connections?.compactMap({ $0.validline }) but I am getting error saying "Type of expression is ambiguous without more context"
I don't see how you can do it without to forEach or compactMap. Here is a possible solution:
func getData() -> [String] {
return item?.connections?.compactMap { connection in
connection.validLine?.compactMap { line in
guard let _ = line.connection?.links[LinkKey.dataGroups], line.dataGroupsCache?.isContinue == true else { return nil }
return checkinGroups.connections?.compactMap(\.id)
}
}
}
Here's a translation of your post into something that is compilable and a direct translation into a version that doesn't use forEach.
I changed connectionIds to ids in your example because otherwise, you might as well just return [].
class Example {
func getData() -> [String] {
var ids = [String]()
self.item?.connections?.forEach { connection in
connection.validLine?.forEach { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
ids += checkinGroups.connections?.compactMap { $0.id } ?? []
}
}
}
return ids
}
func getDataʹ() -> [String] {
guard let connections = item?.connections else { return [] }
let numberOfProperLines = connections.flatMap { $0.validLine ?? [] }
.filter { line in
if let _ = line.connection?.links[LinkKey.dataGroups],
let dataGroups = line.dataGroupsCache, dataGroups.isContinue {
return true
} else {
return false
}
}
.count
return (0..<numberOfProperLines).flatMap { _ in checkinGroups.connections?.compactMap(\.id) ?? [] }
}
var checkinGroups: CheckInGroups!
var item: Item!
}
enum LinkKey: Int {
case dataGroups
}
struct Item {
let connections: [Connection]?
}
struct Connection {
let id: String?
let validLine: [Line]?
let links: [LinkKey: Void]
}
struct Line {
let dataGroupsCache: DataGroups?
let connection: Connection?
}
struct DataGroups {
let isContinue: Bool
}
struct CheckInGroups {
let connections: [Connection]?
}

IGListKit insert data to class from Alamofire request

So i have this model
class Event: NSObject {
var _eventName: String!
var _venueName : String!
var _eventImage: String!
var eventName: String {
if _eventName == nil {
_eventName = ""
}
return _eventName
}
var venueName: String {
if _venueName == nil {
_venueName = ""
}
return _venueName
}
var eventImage: String {
if _eventImage == nil {
_eventImage = ""
}
return _eventImage
}
init(eventsDict: Dictionary<String, AnyObject>) {
if let venue = eventsDict["venue"] as? Dictionary<String, AnyObject> {
if let venuname = venue["name"] as? String{
self._venueName = venuname
}
if let eventname = eventsDict["name"] as? String {
self._eventName = eventname
}
if let eventimage = eventsDict["coverPicture"] as? String {
self._eventImage = eventimage
}
}
}
And i make it IGListDiffable with this extension.
extension NSObject: IGListDiffable {
public func diffIdentifier() -> NSObjectProtocol {
return self
}
public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool {
return isEqual(object)
}
}
So when I'm loading data from hardcoded code like this
var entries = [Event]()
func loadFakeEvents() {
let entries = [
Event(
eventName: "Ζωρζ Πιλαλι Και Η Soufra Band Στο AN Groundfloor - Live Stage!",
venueName: "AN Groundfloor - live stage",
eventImage: "https://scontent.xx.fbcdn.net/v/t31.0-8/s720x720/15936729_1867160333520142_8855370744955080264_o.jpg?oh=8198bc10a8ea61011d7ec1902b34aa01&oe=593D6BC4"
),
Event(
date: "2017-02-18T21:30:00+0200",
name: "Διονύσης Σαββόπουλος at Gazarte I Main Stage 18/02",
venuename: "Gazarte",
eventImage: "https://scontent.xx.fbcdn.net/v/t1.0-9/s720x720/16265335_1262826863809003_3636661375515976849_n.jpg?oh=5bb342321a65d33dbc1cc41de266b45e&oe=5907857C"
)
]
self.entries = entries
}
The events are loading fine. As they have to.
But when i'm making an alamofire request, of course, it takse some time to load the data and append them to the empty array of events.
This is the function that I have to call the events
func loadEvents() {
let parameters: Parameters = [
"Some" : "Parameters",
"Some" : "Parameters"
]
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if((responseData.result.value) != nil) {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
print(dict) // <-- Check this out
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
}
}
}
}
}
So in the above code i have a print, which prints the json.
And in my
extension LocationViewController: IGListAdapterDataSource {
func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] {
let items: [IGListDiffable] = loader.entries as [IGListDiffable]
print(items.count) // <--- another print of items that should be displayed
return items
}
func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController {
return NormalSectionController()
}
func emptyView(for listAdapter: IGListAdapter) -> UIView? { return nil }
}
Adapter i also print the items that should be displayed.
So when i load the fakeEvents function it prints 2 but when i load them with the normal function it prints 0 and then the JSON from the dict var from the previous code.
Normally i would reloadData() of the collection view.
But with IGListKit what is the trick of sending the Event Class to the CollectionView?
Thanks a lot for your time and i hope i'm not off topic !
Pasting my answer from this same issue on Github in case anyone finds this.
https://github.com/Instagram/IGListKit/issues/468
It looks like you're missing a call to self.adapter.performUpdates(animated: true) after the for-loop when appending to the entries dict:
func loadEvents() {
// ...
Alamofire.request(baseurl, method: .get, parameters: parameters)
.responseJSON { (responseData) -> Void in
if responseData.result.value != nil {
let result = responseData.result
if let dict = result.value as? Dictionary<String, AnyObject>{
if let list = dict["events"] as? [Dictionary<String, AnyObject>] {
for obj in list {
let event = Event(eventsDict: obj)
self.entries.append(event)
}
// missing this!
self.adapter.performUpdates(animated: true)
// missing that!
}
}
}
}
}

Dispatch group functions not called - how to use dispatch group?

I have several functions to retrieve data from firebase. I want an activity indicator to spin until all the data is retrieved.
My problem is that, the functions aren't even started. To search for the mistake I've entered print statements which aren't called.
This is my code:
override func viewDidLoad() {
super.viewDidLoad()
loadingActInd.hidesWhenStopped = true
self.loadingActInd.startAnimating()
let group = dispatch_group_create()
dispatch_group_enter(group)
func loadTweetComplete() {
dispatch_group_leave(group)
}
dispatch_group_enter(group)
func loadEventsComplete() {
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
self.loadingActInd.stopAnimating()
print("deejayTweetsDictionary = \(deejayTweetsDictionary)")
print("finished executing")
}
}
func loadTweetComplete(completionHandler: () -> ()) {
print("TEST")
deejayTweetsDictionary.removeAll()
let usersRef = firebase.child("DeejayTweets").child(passedDJ.objectId)
usersRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.exists() {
deejayTweetsDictionary.removeAll()
let sorted = (snapshot.value!.allValues as NSArray).sortedArrayUsingDescriptors([NSSortDescriptor(key: "date",ascending: false)])
for element in sorted {
deejayTweetsDictionary.append(element as! NSMutableDictionary)
}
}
completionHandler()
})
}
func loadEventsComplete(completionHandler: () -> ()) {
print("TEST")
eventDates.removeAll()
if passedDJ.objectId != nil {
let userRef = firebase.child("djBookings").child(passedDJ.objectId)
userRef.observeSingleEventOfType(.Value, withBlock: { (snapshot) in
if snapshot.exists() {
eventDates.removeAll()
let item = (snapshot.value as? NSMutableDictionary)!
let allValues = item.allValues
for element in allValues {
eventDates.append(element as! NSMutableDictionary)
}
}
completionHandler()
})
}
}
The Indicator spins forever and not even the print("TEST") statements are called. What am I doing wrong? Help is very appreciated.
func loadTweetComplete() {
dispatch_group_leave(group)
}
defines a (nested) function. What you want is to call the
function with the given completion handler as an argument.
Using the trailing closure syntax that would be
loadTweetComplete {
dispatch_group_leave(group)
}

Swift Scoping Rules -- nil value after setting a non-nil value

I'm writing a DataService that interfaces with Firebase. I'm setting self.realID within a closure and when I reference it outside the closure, it fails because it unwraps a nil value. Why is this happening?
My file:
import Foundation
import Firebase
class Database {
var firebaseRef = Firebase(url:"https://<<UNIQUE>>.firebaseio.com")
class var sharedInstance: Database {
struct Data {
static var instance: Database?
static var token: dispatch_once_t = 0
}
dispatch_once(&Data.token) {
Data.instance = Database()
}
return Data.instance!
}
var uid : String!
var realID : String!
var validated = false
func validate(user: String, study: String) -> Bool {
firebaseRef.authUser(user+"#example.com", password: user,
withCompletionBlock: { error, authData in
if error != nil {
NSLog(String(error))
} else {
self.uid = authData.uid
NSLog(authData.uid)
}
})
let usersRef = Firebase(url: "https://<<UNIQUE>>.firebaseio.com/users")
usersRef.observeEventType(FEventType.Value, withBlock: { (snapshot) in
let value = snapshot.value.objectForKey("study") as! String
self.realID = value
NSLog(self.realID) // this is a non-nil value
})
NSLog("About to encounter nil value and crash")
if self.realID == study {
return true
}
return false
}
}
How do i prevent this fatal error from happening?
You need to add a completionHandler because it is async request. If you will set the break points then return is executed before you are setting the id.
func validate(user: String, study: String, completionHandler:(Bool) -> Void) {
let usersRef = Firebase(url: "https://<<UNIQUE>>.firebaseio.com/users")
usersRef.observeEventType(FEventType.Value, withBlock: { (snapshot) in
if let value = snapshot.value.objectForKey("study") as? String {
self.realID = value
completionHandler(true)
} else {
completionHandler(false)
}
})
}
UPDATE
validate("Rahul", study: "Study") { (value: Bool) in
if value {
} else {
}
}

Firebase Retrieving and passing data to variable

ridvankucuk Problem
func getHandleStatus() -> String {
var handle = String()
ref.queryOrderedByChild("status").observeEventType(.ChildAdded, withBlock: { snapshot in
if let status = snapshot.value["status"] as? Int {
if status == 0 {
//print(snapshot.key)
handle = snapshot.key
} else {
print("NO")
}
}
})
return handle
}
When i return "handle", its always empty string. I set the variable "handle = snapshot.key" and when i return, its still empty. Please help.
First of all your method is async and it is not waiting the main thread. So writing return makes no sense. You should do something else.
Define a variable outside of the function such as:
var handle: String? = nil {
didSet{
// You know handle is set now
}
}
And update your getHandleStatus method:
func getHandleStatus(){
var handle = String()
ref.queryOrderedByChild("status").observeEventType(.ChildAdded, withBlock: { snapshot in
if let status = snapshot.value["status"] as? Int {
if status == 0 {
//print(snapshot.key)
self.handle = snapshot.key
} else {
print("NO")
}
}
})
}

Resources