Prevent GKGridGraph crashing while pathfinding - swift

In my App the Pink heart want to go to the red one! But as you see the way is blocked.
The code I use is:
let solution = GridGraph.findPath(from: GridGraph.node(atGridPosition:int2(Int32(pinkHeart.positon.x),Int32(pinkHeart.positon.y)))!,to:GridGraph.node(a[enter image description here][1]tGridPosition:int2(Int32(redHeart.positon.x),Int32(redHeart.positon.y)))!) as! [GKGridGraphNode]
But the Application always crashes because there is no way!
If I want to find a path from the pink heart to the yellow/green one the code works.
I tried many different ways to solve the problem but nothing seems to work...
e.g.:
do {
let solution = try GridGraph.findPath(from:GridGraph.node(atGridPosition:int2(Int32(pinkHeart.positon.x),Int32(pinkHeart.positon.y)))!, to:GridGraph.node(atGridPosition: int2(Int32(redHeart.positon.x),Int32(redHeart.positon.y)))!) as! [GKGridGraphNode]
} catch {
print("no path found")
}
or
if let solution = GridGraph.findPath(from:GridGraph.node(atGridPosition:int2(Int32(pinkHeart.positon.x),Int32(pinkHeart.positon.y)))!, to:GridGraph.node(atGridPosition: int2(Int32(redHeart.positon.x),Int32(redHeart.positon.y)))!) as! [GKGridGraphNode] {
//Do something!
}
I tried many more possible solutions, but it always crashed...
Thank you for your help!
Thats the image:

You can do it this Way:
var solutionPath: [GKGridGraphNode]? {
let solution = GridGraph.findPath(from: OneNode, to: anotherNode) as! [GKGridGraphNode]
if solution.isEmpty {
print("There is no possible path!")
return nil
}
else {
return solution
}
}

Related

Can't fill my collection views with API data by using Alamofire

There is an api (https://docs.api.jikan.moe/#section/Information). I get data from it, but I can’t display them in my collection views in any way. The data should come, I checked. I implement filling the collection view cells through the view model ViewController <-> ViewModel and with Network Manager API Manager
The result is just white collectionView - Screen
For the first time I decided to work with Alamofire and apparently I don’t understand something. Please tell me what is the problem. Link to github in case someone needs it.
Updated
The problem might be with asynchronous coding. And i still have no ideas to fix it, cause don't understand the GCD as well. Screen
func fetchRequest(typeRequest: TypeRequest) -> [AnimeModel] {
var animeModels: [AnimeModel] = []
switch typeRequest {
case .name(let name):
let urlString = "https://api.jikan.moe/v4/anime?q=\(name)"
AF.request(urlString).response { response in
guard let data = response.data else { return print("NO DATA FOR - \(name)") }
do {
let json = try JSON(data: data)
let title = json["data"][0]["title_english"].string ?? "Anime"
let imageURL = json["data"][0]["images"]["jpg"]["image_url"].string ?? ""
let image = AnimeModel.downloadImage(stringURL: imageURL)
animeModels.append(AnimeModel(image: image, title: title))
print(".NAME ANIME MODELS - \(animeModels)")
} catch let error {
print(error.localizedDescription)
}
}
}
print("BEFORE RETURN ANIME MODELS - \(animeModels)")
return animeModels // returns empty array and then "animeModel.append()" is applied
}

Access user's first name and other information in firebase using Swift

I am building my first ever IOS app with the help of online resources. I was able to build the signup and login screens of the app and connect it to firebase and store user's information on there. However, I want to get (retrieve) user's first name from the database when they log in and display that, and I found a piece of code that successfully does that but it is very slow, everything else in the page loads before it, so it is not really an ideal solution. I was wondering if there was another way to achieve what I want to do.
Code that checks if user's is signed in and gets its first name:
func CheckIfUserIsSignedIn() {
let db = Firestore.firestore()
if let userId = Auth.auth().currentUser?.uid {
let userName = db.collection("users").getDocuments() { (snapshot, error) in
if let error = error {
self.errorLabel.textColor = UIColor.red
self.errorLabel.text = "Error getting documents: \(error)"
}
else {
for document in snapshot!.documents {
if let firstUserDoc = snapshot?.documents.first {
let welcomeName = firstUserDoc["first_name"] as! String
self.errorLabel.text = "Hey, \(welcomeName) welcome!"
}
}
} //end else
}
} //end if
else {
//User is not logged in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "login")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
} //end CheckIfUserIsSignedIn method`
There are a few ways you can do this but just one thing I want to point out is that when you check if the user has logged in with if let userId = Auth.auth().currentUser?.uid you then proceed to retrieve every user document with let userName = db.collection("users").getDocuments() which is probably making it much more slower than it needs to be. And this will get slower the more popular your app becomes because downloading more users takes more time. This is an easy fix by just adding one small thing:
let userName = db.collection("users").document(userId).getDocument()
This only gets 1 document instead of all.
Also right after that you loop through each document you've retrieved and perform
if let firstUserDoc = snapshot?.documents.first {
let welcomeName = firstUserDoc["first_name"] as! String
self.errorLabel.text = "Hey, \(welcomeName) welcome!"
}
This block is run snapshot!.documents.count (Number of users you have in your app) amount of times which again seems unnecessary as it does the same thing each iteration. Remove the loop and doing it 1 time will be so much faster.
This is how your code should look after:
if let userId = Auth.auth().currentUser?.uid {
db.collection("users").document(userId).getDocument { docSnapshot, error in
if let error = error {
errorLabel.textColor = UIColor.red
errorLabel.text = "Error getting documents: \(error)"
} else {
let welcomeName = docSnapshot!.get("first_name") as! String
errorLabel.text = "Hey, \(welcomeName) welcome!"
}
}
} //end if
// ...Other code
This should work but if you want an even faster way to do this and don't use the Auth.auth().currentUser!.displayName property then you can store their first name in that and simply reduce your code to:
if let userFirstName = Auth.auth().currentUser?.displayName! {
errorLabel.text = "Hey, \(userFirstName) welcome!"
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "login")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
^ This would be ideal if your app refers to all users by their first names instead of their usernames (plus it'll show up in verification emails as well)
One last thing I'd like to mention is in your original post I don't understand how you were guaranteeing the first document of the snapshot to be the user you want. If it's an old user using the app then someone else's name would probably come up as newer users will be at the top of the list. As this is your first app I want to stress the importance of writing tests for your functions. Be sure to read up on Unit Tests and UI Tests (mainly Unit Tests for your purposes), they really make a big difference. It's not too hard to learn either. I remember when I first started I avoided them like the plague because I thought they took too much time. But in the long run they save you thousands of hours by making your code as bug free as possible and even help structuring your code better by making it more modular!
Hope this helps and best of luck with your first app!

SWIFT 5: Increment Value Async of Determinate Circular Progress Indicator

I searched a lot but I can't find a good explanation of using a determinate circular progress indicator.
I have a Helper-Class that converts a PDF to an image. I have more than 400 pages so I decided to make these things asynchron with the following code:
DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
… do the work …
view.incrementProgressIndicator()
}
Insight the view, where the ProgressIndicator lives, the called function looks the following:
func incrementProgressIndicator() {
self.progressIndicator.increment(by: 1)
}
For sure, Swift says: "UI API called on a background thread".
I tried to surround the code of incrementing with:
DispatchQueue.main.async {
self.progressIndicator.increment(by: 1)
}
But now, all the work of indicating is done after the conversion of PDF is completed.
Can someone tell my how this can be done? I can't find a working tutorial for my issue.
Thank you for your help!
I found the solution for my issue. I replace the
DispatchQueue.concurrentPerform(iterations: pdfDocument.numberOfPages) { i in
… do the work …
view.incrementProgressIndicator()
}
with the following code:
for i in 0..<pdfDocument.numberOfPages {
DispatchQueue.global(qos: .background).async {
let pdfPage = pdfDocument.page(at: i + 1)!
if let result = self.convertPageToImage(withPdfPage: pdfPage, sourceURL: sourceURL, destUrl: destinationURL, index: i) {
urls[i] = result
}
DispatchQueue.main.async {
if let delegate = self.delegate {
print("Ask for Increment")
delegate.incrementProgressIndicator()
}
}
}
}

How do I check if userDefault is empty?

I'm trying to deny access to a certain view controller if the userDefault is empty, but the code doesn't seem to work. To be a bit more clear, I'm saving a favorite-list to a userDefault. This is my code:
if UserDefaults.standard.array(forKey: "favorites") == nil {
navigationController?.popToRootViewController(animated: true)
return
}
The error is Index out of range, which means that the whole block is ignored (the code after this block runs and since the user default is empty it crashes when trying to retrieve information that isn't there).
The funny thing is, the code works the first time I try to enter the viewController (it denies me access). But if I favorite mark an object (save to userDefault), then un-favorite the same object (userDefault becomes empty), and enter the viewController, the program crashes.
I have tried:
if let favExist = UserDefaults.standard.array(forKey: "favorites") {
print("")
print("FAV EXISTS")
print("")
}else {
print("")
print("NOPE")
print("")
navigationController?.popToRootViewController(animated: true)
return
}
...and the same problem persists. In print() the log tells me FAV EXISTS after I favorite mark, then un-favorite mark, then try accessing the page (even though the userDefault now should be empty).
I have also tried code from other threads. The suggested code to solve my problem from the other thread was:
let defaults = UserDefaults.standard
if (!defaults.bool(forKey: "favorites")) {
defaults.set(true, forKey: "favorites")
}
I'm not really sure how to implement it though? Where do I use this? And what does it do?
Any idea what's wrong?
It´s enough to do this:
if let favorites = UserDefaults.standard.array(forKey: "favorites") {
// userDefault has a value
} else {
// userDefault is nil (empty)
}
Update:
You need to make a check within the if-statement if your arrat has any values too:
if let favorites = UserDefaults.standard.array(forKey: "favorites") {
print("Favorites exists")
if favorites.isEmpty {
print("Favorites is empty")
} else {
print("Favorites is not empty, it has \(favorites.count) items")
}
} else {
print("Favorites is nil")
}
When you set the UserDefaults Array also set a BOOL to UserDefaults. When you recover the Bool it won't crash even if it hasn't been set.
var favouritesset = UserDefaults.standard.bool(forKey: "favoritesset")
if favouritesset == true {
//Then Recover the Array
var array = UserDefaults.standard.array(forKey: "favorites")
}
OK, Rashwan L solved it for me. Thing was, my code (and suggested code by others) checked whether or not userDefault existed or not - it didn't bother whether there was a value stored or not. To work around the problem, I had to test if favExist.count == 0 was true. If true, user is blocked from the page and prevented from accessing the rest of the code. Se below:
if let favExist = UserDefaults.standard.array(forKey: "favorites") {
if(favExist.count == 0)
{
navigationController?.popToRootViewController(animated: true)
return
}
}else {
navigationController?.popToRootViewController(animated: true)
return
}
You do like this:
if UserDefaults.standard.array(forKey: "favs") != nil {
// userDefault has a value
} else {
// userDefault is nil (empty)
}

How to make phone calls in swift

First of all I know there are some similar topics as this one but because of my reputation I couldn't comment on those for help and stack overflow warned me not to ask for help from the answers section.. none of the similar posts have answered my question so here I go.
As can be understood from the topic, I want make a phone call on click,
I'm making an app for my business and I want to put in a call button so that people can call me over the app.
here are the attempts I've tried as read from the similar topics:
let phoneNumber = "1234567890"
if let phoneCallURL = NSURL(string: "tel:\(phoneNumber)") {
let application = UIApplication.sharedApplication()
if application.canOpenURL(phoneCallURL) {
application.openURL(phoneCallURL)
}
else{
println("failed")
}
}
so when I run the above code with a phone number it prints out the failed message on the console seems like i fails opening the URL
The other code I've tried is a very similar one
var url:NSURL = NSURL(string: "tel://phoneNumber")!
UIApplication.sharedApplication().openURL(url)
one other question is that: What is the correct syntax for the NSURL?
this
NSURL(string: "tel://\(phoneNumber)")
or this
NSURL(string: "tel//:\(phoneNumber)")
My last question is: If the app manages to make a call, does it appear on the simulator like a calling screen? I'm very new to swift programming and I apologise if the questions seem stupid..
The simple format for a tel: URL is tel:######. / is not a number. You probably mean this to be:
NSURL(string: "tel:\(phoneNumber)")
assuming phoneNumber is a string containing the phone number.
The tel: scheme is defined in RFC2806. You can look there for details on all its expected features.
Note that phone calls are not possible in the simulator, so you'll receive an error if you try to open a tel: URL there (unless you handle the URL yourself by registering an NSURLProtocol).
If you want to return to your app after your call has been ended (which you should do) then you need to use telprompt:// instead of tel://. The tel:// will take you to the home screen after the call.
Better use this:
var url:NSURL = NSURL(string: "telprompt://1234567891")!
UIApplication.sharedApplication().openURL(url)
let phoneNumber = "0507712323"
if let callNumber = phoneNumber {
let aURL = NSURL(string: "telprompt://\(callNumber)")
if UIApplication.sharedApplication().canOpenURL(aURL) {
UIApplication.sharedApplication().openURL(aURL)
} else {
print("error")
}
}
else {
print("error")}
I have this issue for different reasons and I would like share with you .
First Dont try in simulator must try on real device
Second make sure the passed number dont contain space
here is example
private func callNumber(phoneNumber:String) {
// I add this line to make sure passed number wihthout space
let CleanphoneNumber = phoneNumber.stringByReplacingOccurrencesOfString(" ", withString: "")
if let phoneCallURL:NSURL = NSURL(string: "tel://\(CleanphoneNumber)") {
let application:UIApplication = UIApplication.sharedApplication()
if (application.canOpenURL(phoneCallURL)) {
application.openURL(phoneCallURL);
}
}
}
Your code looks correct.It seems that it would always fail if you test it in simulator.
Try to use your iPhone to run it,and it would go to dialer interface as you want.
i added some additional validation in the code
func makeCall(constactNumber : NSString)
{
if(constactNumber.length == 0)
{
print("Contact number in not valid")
}
else
{
let CleanconstactNumber = constactNumber.stringByReplacingOccurrencesOfString(" ", withString: "")
if let phoneCallURL:NSURL = NSURL(string: "tel://\(CleanconstactNumber)")
{
if (UIDevice.currentDevice().model.rangeOfString("iPad") != nil)
{
print("Your device doesn't support this feature.")
} else
{
let application:UIApplication = UIApplication.sharedApplication()
if (application.canOpenURL(phoneCallURL))
{
let mobileNetworkCode = CTTelephonyNetworkInfo().subscriberCellularProvider?.mobileNetworkCode
if( mobileNetworkCode == nil)
{
print(" No sim present Or No cellular coverage or phone is on airplane mode.")
}
else
{
application.openURL(phoneCallURL);
}
}
}
}
}
}