I'm very new to Parse and Swift and I have this project I am working on and I am trying to create a search bar that displays all the items from the key "names" from my Parse database.
I have created this function that is supposed to take all the names and return them in a string array. But instead, the array never gets filled and all I get as a return is [].
class Offices {
var name: String
var phone: String
var location: String
init(name: String = "def_name", phone: String = "def_phone", location: String = "def_location") {
self.name = name
self.phone = phone
self.location = location
}
func retrieveName() -> [String] {
var models = [String]()
let queries = PFQuery(className: "Directory")
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
} else if let object = object {
// The query succeeded with a matching result
for i in object{
models.append(i["name"] as? String ?? self.name)
}
} else {
// The query succeeded but no matching result was found
}
}
return models
}
findObjectsInBackground method is asynchronous. So you should change retrieveName function as below:
class Offices {
var name: String
var phone: String
var location: String
init(name: String = "def_name", phone: String = "def_phone", location: String = "def_location") {
self.name = name
self.phone = phone
self.location = location
// I call retrieveName here for example. You can call it where you want.
retrieveName() { (success, models) in
if success {
print(models)
} else {
print("unsuceess")
}
}
}
func retrieveName(completion: #escaping (_ success: Bool, _ models: [String]) -> Void) {
var models = [String]()
let queries = PFQuery(className: "Directory")
queries.findObjectsInBackground { (object, error) in
if let error = error {
// The query failed
print(error.localizedDescription)
completion(false, [])
} else if let object = object {
// The query succeeded with a matching result
for i in object{
models.append(i["name"] as? String ?? self.name)
}
completion(true, models)
} else {
completion(true, [])
// The query succeeded but no matching result was found
}
}
}
}
Related
Im trying to delete a realm object which contains a realm list of objects, but when I do that I receive this error: "Thread 1: RLMArray has been invalidated or the containing object has been deleted.". I've tried all the solutions I've managed to find but none of them helped me to fix my issue. Thank you for helping in advance!
Parent object creation code :
#MainActor
func addWordSet(name : String){
Task {
do {
// let realm = try await realmService.getRealm()
if let realm = realmService.getRealm() {
let user = realmService.getCurrentUser()
let set = WordSet(name: name, ownerId: user?.id ?? "")
try realm.write {
realm.add(set, update: Realm.UpdatePolicy.modified)
}
}
}catch {
print(error)
}
}
}
Child object creation code
#MainActor
func addTranslationToSet(set : WordSet, term : String, meaning : String, completion: #escaping () -> Void){
Task {
do {
if let realm = realmService.getRealm() {
let user = realmService.getCurrentUser()
let translation = Translation(word: term, translation: meaning, ownerId: user?.id ?? "")
try realm.write {
set.translations.append(translation)
}
completion()
}
}catch {
print(error)
}
}
}
Parent object deletion code:
#MainActor
func deleteWordSet(set : WordSet){
Task {
do {
if let realm = realmService.getRealm() {
try realm.write {
realm.delete(set)
}
}
} catch{
print(error)
}
}
}
#MainActor
func deleteTranslationFromSet(set : WordSet, translation : Translation, completion: #escaping () -> Void){
Task {
do {
if let realm = realmService.getRealm() {
let index = set.translations.firstIndex{
$0._id == translation._id
}
try realm.write {
if let index = index {
set.translations.remove(at: index)
}
}
completion()
}
}catch {
print(error)
}
}
models code :
class Translation : Object {
#Persisted(primaryKey: true) var _id : ObjectId
#Persisted var owner_id : String
#Persisted var word : String
#Persisted var translation : String
convenience init(word : String, translation: String, ownerId: String){
self.init()
self.word = word
self.translation = translation
self.owner_id = ownerId
}
}
class WordSet : Object {
#Persisted var name : String
#Persisted(primaryKey: true) var _id : ObjectId
#Persisted var owner_id : String
#Persisted var translations : List<Translation>
convenience init(name : String, ownerId : String){
self.init()
self.name = name
self.owner_id = ownerId
}
}
When I update the firebase firestore database with any new field, it instantly kills any app running that uses the data with the fatal error in the code below.
The error I get says "fatalError: "Unable to initialize type Restaurant with dictionary [(name: "test", availability: "test", category: "test")]
I'd like to be able to update it without having the apps crash. If that happens, they have to delete and reinstall the app to get it to work again, so I think it's storing the data locally somehow, but I can't find where.
What can I do to make this reset the data or reload without crashing?
The file where the error is thrown (when loading the table data):
fileprivate func observeQuery() {
stopObserving()
guard let query = query else { return }
stopObserving()
listener = query.addSnapshotListener { [unowned self] (snapshot, error) in
guard let snapshot = snapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> Restaurant in
if let model = Restaurant(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data())")
}
}
self.restaurants = models
self.documents = snapshot.documents
if self.documents.count > 0 {
self.tableView.backgroundView = nil
} else {
self.tableView.backgroundView = self.backgroundView
}
self.tableView.reloadData()
}
}
And the Restaurant.swift file:
import Foundation
struct Restaurant {
var name: String
var category: String // Could become an enum
var availability: String // from 1-3; could also be an enum
var description: String
var dictionary: [String: Any] {
return [
"name": name,
"category": category,
"availability": availability,
"description": description
]
}
}
extension Restaurant: DocumentSerializable {
//Cities is now availability
static let cities = [
"In Stock",
"Back Order",
"Out of Stock"
]
static let categories = [
"Rock", "Boulder", "Grass", "Trees", "Shrub", "Barrier"
]
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let category = dictionary["category"] as? String,
let availability = dictionary["availability"] as? String,
let description = dictionary["description"] as? String
else { return nil }
self.init(name: name,
category: category,
availability: availability,
description: description
)
}
}
The Local Collection File with the Document.Serializable code:
import FirebaseFirestore
// A type that can be initialized from a Firestore document.
protocol DocumentSerializable {
init?(dictionary: [String: Any])
}
final class LocalCollection<T: DocumentSerializable> {
private(set) var items: [T]
private(set) var documents: [DocumentSnapshot] = []
let query: Query
private let updateHandler: ([DocumentChange]) -> ()
private var listener: ListenerRegistration? {
didSet {
oldValue?.remove()
}
}
var count: Int {
return self.items.count
}
subscript(index: Int) -> T {
return self.items[index]
}
init(query: Query, updateHandler: #escaping ([DocumentChange]) -> ()) {
self.items = []
self.query = query
self.updateHandler = updateHandler
}
func index(of document: DocumentSnapshot) -> Int? {
for i in 0 ..< documents.count {
if documents[i].documentID == document.documentID {
return i
}
}
return nil
}
func listen() {
guard listener == nil else { return }
listener = query.addSnapshotListener { [unowned self] querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshot results: \(error!)")
return
}
let models = snapshot.documents.map { (document) -> T in
if let model = T(dictionary: document.data()) {
return model
} else {
// handle error
fatalError("Unable to initialize type \(T.self) with local dictionary \(document.data())")
}
}
self.items = models
self.documents = snapshot.documents
self.updateHandler(snapshot.documentChanges)
}
}
func stopListening() {
listener = nil
}
deinit {
stopListening()
}
}
fatalError: "Unable to initialize type Restaurant with dictionary [(name: "test", availability: "test", category: "test")]
Seems pretty straightforward - that dictionary does not contain enough information to create a Restaurant object.
The error is from
if let model = Restaurant(dictionary: document.data()) {
return model
} else {
// Don't use fatalError here in a real app.
fatalError("Unable to initialize type \(Restaurant.self) with dictionary \(document.data())")
}
because your initializer returns a nil value, from:
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let category = dictionary["category"] as? String,
let availability = dictionary["availability"] as? String,
let description = dictionary["description"] as? String
else { return nil }
self.init(name: name,
category: category,
availability: availability,
description: description
)
}
because your guard is returning nil because you do not have a description key in the dictionary.
To fix, either put a description key in the dictionary OR change your initializer to use a default description when the key is missing.
For example, here is your initializer, rewritten to use a default description, for when the description entry is missing
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String,
let category = dictionary["category"] as? String,
let availability = dictionary["availability"] as? String
else { return nil }
let description = dictionary["description"] as? String
let defaultDescription: String = description ?? "No Description"
self.init(name: name,
category: category,
availability: availability,
description: defaultDescription
)
}
I have problem adding annotations to mapView. I had success with this code:
func placeAnnotations() {
for _ in placeDetails {
let multipleAnnotations = MKPointAnnotation()
multipleAnnotations.title = place.address
multipleAnnotations.subtitle = place.phone
multipleAnnotations.coordinate = CLLocationCoordinate2D(latitude: place.lat, longitude: place.lng)
mapView.addAnnotation(multipleAnnotations)
}
}
Problem is, it is not conforming to my Place class, thus not showing custom Title, Subtitle and MKAnnotationView. This is code inside viewDidLoad(), where I'm trying to put all the annotations, but it keeps adding only last one. I understood that it overrides all the previous ones from array, but haven't found any other way/method to implement.
var placeDetails = [Place]()
var places = [Place]()
override func viewDidLoad() {
super.viewDidLoad()
downloadPlaceID {
for obj in places {
place.downloadDetails(input: obj.placeId, completed: {
self.placeDetails.append(obj)
//self.placeAnnotations()
self.mapView.addAnnotations(self.placeDetails)
})
}
}
}
And this is my class with all the data conforming to MKAnnotation
protocol, and functions, downloadPlaceID() and downloadDetails()
class Place: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var placeId: String!
var vicinity: String!
var phone: String!
var workHours: Bool!
var lat: Double!
var lng: Double!
var address: String!
var subtitle: String? {
return phone
}
var title: String? {
return address
}
var _placeId: String {
if placeId == nil {
placeId = ""
}
return placeId
}
var _vicinity: String {
if vicinity == nil {
vicinity = ""
}
return vicinity
}
var _phone: String {
if phone == nil {
phone = ""
}
return phone
}
var _workHours: Bool {
if workHours == nil {
workHours = false
}
return workHours
}
var _lat: Double {
if lat == nil {
lat = 0.0
}
return lat
}
var _lng: Double {
if lng == nil {
lng = 0.0
}
return lng
}
var _address: String {
if address == nil {
address = ""
}
return address
}
init(place: [String:Any]) {
if let ids = place["place_id"] as? String {
self.placeId = ids
}
if let vicinities = place["vicinity"] as? String {
self.vicinity = vicinities
}
self.coordinate = CLLocationCoordinate2DMake(0.0, 0.0)
}
func downloadDetails(input: String, completed: #escaping DownloadComplete) {
let details = "\(detailsBaseURL)\(detailsPlaceId)\(input)\(detailsKey)\(detailsSearchAPIKey)"
Alamofire.request(details).responseJSON { response in
let result = response.result
if let dictionary = result.value as? [String:Any] {
if let result = dictionary["result"] as? [String:Any] {
if let phoneNumber = result["formatted_phone_number"] as? String {
self.phone = phoneNumber
}
if let geometry = result["geometry"] as? [String:Any] {
if let location = geometry["location"] as? [String:Any] {
if let latitude = location["lat"] as? Double {
self.lat = latitude
}
if let longitude = location["lng"] as? Double {
self.lng = longitude
}
self.coordinate = CLLocationCoordinate2DMake(self.lat, self.lng)
}
}
if let openingHours = result["opening_hours"] as? [String:Any] {
if let openNow = openingHours["open_now"] as? Bool {
self.workHours = openNow
}
}
if let addressComponents = result["address_components"] as? [[String:Any]] {
let longName = addressComponents[1]["long_name"] as? String
let shortName = addressComponents[0]["long_name"] as? String
self.address = "\(longName!),\(shortName!)"
}
}
}
completed()
}
}
}
func downloadPlaceID (completed: #escaping DownloadComplete) {
let placeURL = URL(string: nearbyURL)
Alamofire.request(placeURL!).responseJSON { (response) in
let result = response.result
if let dictionary = result.value as? [String:Any] {
if let results = dictionary["results"] as? [[String:Any]] {
if let status = dictionary["status"] as? String {
if status == "OK" {
for obj in results {
place = Place(place: obj)
places.append(place)
}
} else {
print("jede govna")
}
}
}
}
completed()
}
There's some odd mixing of variable names and concepts here that makes your code somewhat hard to understand.
As an example, assigning a variable you call vicinities, plural, to an attribute called vicinity, singular. Or not separating your downloadDetails function from your data model.
That aside, it looks to me like you're unnecessarily adding your MKAnnotations many times to your map, by adding your array of [MKAnnotation] to your map in each loop.
I suspect you've done this because you've made it hard for yourself to know when the whole array is done updating its details.
As a quick fix, I'd suggest changing your downloadDetails function to call the completed function with the place you've just downloaded details for. Here's a really simplified, but working, version of what you are trying to do. First your Place class:
class Place: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
override init() {
coordinate = CLLocationCoordinate2DMake(0, 0)
}
func downloadDetails(completed: #escaping (Place) -> Void) {
// Instead of downloading details we are just creating random positions
self.coordinate = CLLocationCoordinate2DMake(CLLocationDegrees(arc4random_uniform(50)), CLLocationDegrees(arc4random_uniform(50)))
// Return the object you've just built
completed(self)
}
}
Now in your view controller, here I am starting with an array of 50 Place objects, getting the details for them and placing them on the map:
var places = [Place]()
for _ in 1...50 {
places.append(Place())
}
for place in places {
place.downloadDetails(completed: { (placeWithDetails) in
self.mapView.addAnnotation(placeWithDetails)
})
}
This results in the map being populated with 50 random places:
So, i'm trying to create an iOS app (i'm a beginner) that search a movie on IMDB (using OMDb API with Alamofire). The language is swift 3.
After reading a lot of tutorials I did two methods to connect to the API:
func searchMoviesOnJson(imdbTitle: String, completionHandler: #escaping (Dictionary<String, Any>?) -> ()) {
let urlByName: String = "https://www.omdbapi.com/?s=\(imdbTitle)&type=movie"
//returns a list of movies that contains the title searched
Alamofire.request(urlByName).responseJSON {
response in
switch response.result {
case .success(let value):
let moviesJSON = value
completionHandler(moviesJSON as? Dictionary<String, Any>)
case .failure(_):
completionHandler(nil)
}
}
}
func getMovieFromJson(imdbID: String, completionHandler: #escaping (Dictionary<String, String>) -> ()) {
let urlById: String = "https://www.omdbapi.com/?i=\(imdbID)"
Alamofire.request(urlById).responseJSON {
response in
if let moviesJSON = response.result.value {
completionHandler(moviesJSON as! Dictionary<String, String>)
}
}
}
Then, I did my Movie class and get stuck in my MovieDAO class (code above):
class Movie {
let poster: String?
let title: String?
let runtime: String?
let genre: String?
let director: String?
let actors: String?
let plot: String?
let released: String?
let imdbID: String?
let imdbRating: String?
init(poster: String?, title: String?, runtime: String?, genre: String?, director: String?, actors: String?, plot: String?, released: String?, imdbID: String?, imdbRating: String?) {
//checking if is nil
if let isPoster = poster {
self.poster = isPoster
} else {
self.poster = nil
}
if let isTitle = title {
self.title = isTitle
} else {
self.title = nil
}
if let isGenre = genre {
self.genre = isGenre
} else {
self.genre = nil
}
if let isRuntime = runtime {
self.runtime = isRuntime
} else {
self.runtime = nil
}
if let isDirector = director {
self.director = isDirector
} else {
self.director = nil
}
if let isActors = actors {
self.actors = isActors
} else {
self.actors = nil
}
if let isPlot = plot {
self.plot = isPlot
} else {
self.plot = nil
}
if let isReleased = released {
self.released = isReleased
} else {
self.released = nil
}
if let isImdbID = imdbID {
self.imdbID = isImdbID
} else {
self.imdbID = nil
}
if let isImdbRating = imdbRating {
self.imdbRating = isImdbRating
} else {
self.imdbRating = nil
}
}
}
I have a table view controller with a search bar and a table view, when the user type the movie title I would like to show the results in my table view.
How can I make the result of my search bar functions be the variable that my MovieDAO will receive? (Sorry if I sad something wrong, feel free to correct me, please)
My search bar test to get user's text:
func searchBar(_ searchBar: UISearchBar, textDidChange imdbTitle:String) {
print("Movie typed: \(imdbTitle)")
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
print("Movie searched: \(searchBar.text!)")
}
Any orientation, explanation, tutorial indication? Every help will be welcome!
Use ObjectMapper to map the objects https://github.com/Hearst-DD/ObjectMapper. And also use AlamofireObjectMapper to retrieve data https://github.com/tristanhimmelman/AlamofireObjectMapper .you can get the tutorials from these links.
Alamofire.request(url,method: .yourmethod).validate().responseArray{ (response:DataResponse<[yourObjectMapperClass]>) in
}
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!
}
}
}
}
}