i am using Alamofire, SwiftyJSON to get data, but i can not understand how do i use error alert in ViewConroller, so my code here..
class Networking {
static func FetchData() {
AF.request("https://ApiApiApiApi", method: .get).validate().responseJSON { responseJSON -> Void in
switch responseJSON.result {
case .success:
print("Validation Successful")
case .failure(let error):
// Here i need to show alert in viewController
print("\(error)")
}
}
}
}
}
If i got error i need to show alert in the ViewContoller
class ViewContoller: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Networking.FetchData()
}
}
Firstly you can create an enum for error messages.
enum YourErrorMessage: String, Error {
case failed = "There is an error"
}
And then add a completion handler to your method.
class Networking {
static func FetchData(completion: #escaping(YourErrorMessage) -> Void) {
AF.request("https://ApiApiApiApi", method: .get).validate().responseJSON { responseJSON -> Void in
switch responseJSON.result {
case .success:
print("Validation Successful")
case .failure(let error):
completion(.failed)
print("\(error)")
}
}
}
}
Now you can reach the method state(failure, success).
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Networking.FetchData { errorMessage in
print(errorMessage.rawValue)
}
}
}
Related
I am stuck with this situation where I have a custom JSONDecoder struct which contains a private function to decode data, and another function which is exposed, and should return a specific, Decodable type. I would like these functions to throw successively so I only have to write my do/catch block inside the calling component, but I'm stuck with this error on the exposedFunc() function:
Invalid conversion from throwing function of type '(Completion) throws -> ()' (aka '(Result<Data, any Error>) throws -> ()') to non-throwing function type '(Completion) -> ()' (aka '(Result<Data, any Error>) -> ()')
Here is the code:
import Foundation
import UIKit
typealias Completion = Result<Data, Error>
let apiProvider = ApiProvider()
struct DecodableTest: Decodable {
}
struct CustomJSONDecoder {
private static func decodingFunc<T: Decodable>(
_ response: Completion,
_ completion: #escaping (T) -> Void
) throws {
switch response {
case .success(let success):
try completion(
JSONDecoder().decode(
T.self,
from: success
)
)
case .failure(let error):
throw error
}
}
static func exposedFunc(
value: String,
_ completion: #escaping (DecodableTest) -> Void
) throws {
apiProvider.request {
try decodingFunc($0, completion)
}
}
}
class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
do {
try CustomJSONDecoder.exposedFunc(value: "test_value") { result in
// Do something with result
}
} catch {
print(error)
}
}
}
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
Thank you for your help
This defines method that takes a non-throwing function:
class ApiProvider: NSObject {
func request(_ completion: #escaping (Completion) -> ()) {
}
}
So in all cases, this function must take a Completion and return Void without throwing. However, you pass the following:
apiProvider.request {
try decodingFunc($0, completion)
}
This method does throw (note the uncaught try), so that's not allowed. You need to do something if this fails:
apiProvider.request {
do {
try decodingFunc($0, completion)
} catch {
// Here you must deal with the error without throwing.
}
}
}
Not using concurrency features is making your code hard to understand. Switch!
final class CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Task {
let result = DecodableTest()
// Do something with result
}
}
}
extension DecodableTest {
init() async throws {
self = try JSONDecoder().decode(Self.self, from: await APIProvider.data)
}
}
enum APIProvider {
static var data: Data {
get async throws { .init() }
}
}
This question already has answers here:
Returning data from async call in Swift function
(13 answers)
Closed 3 years ago.
I want the getPhotos class method to be executed to the end, and only then the code after. Otherwise, the photos are not displayed in the collection. How can this be achieved?
// MARK: - Text Field Delegate
extension CollectionViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
collectionManager.getPhotos(searchTerm: textField.text!)
print("finish")
self.collectionView?.reloadData()
return true
}
}
Implementation of the getPhotos method:
func getPhotos(searchTerm: String) -> [FlickrPhoto] {
loader.searchFlickr(for: searchTerm) { searchResults in
switch searchResults {
case .error(let error):
print("Error Searching: \(error)")
case .results(let results):
self.searchPhotos = results.searchResults
}
}
return searchPhotos
}
you can use this closure:
func getPhotos(searchTerm: String, onCompletion: #escaping ([FlickrPhoto]) -> Void) {
loader.searchFlickr(for: searchTerm) { searchResults in
switch searchResults {
case .error(let error):
print("Error Searching: \(error)")
onCompletion([FlickrPhoto]())
case .results(let results):
self.searchPhotos = results.searchResults
onCompletion(results.searchResults)
}
}
return searchPhotos
}
and on completion call:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
collectionManager.getPhotos(searchTerm: textField.text!) { results
[weak self] in
print("finish")
self.searchPhotos = results
self?.collectionView?.reloadData()
}
return true
}
You need to add a completion handler to your getPhotos method instead of the return. This is because getPhotos is asynchronous, so the completion handler is kind of what needs to be done after it finishes.
func getPhotos(searchTerm: String, completion: #escaping [FlickrPhoto]? -> ()) {
loader.searchFlickr(for: searchTerm) { searchResults in
switch searchResults {
case .error(let error):
print("Error Searching: \(error)")
case .results(let results):
self.searchPhotos = results.searchResults
}
}
return searchPhotos
}
and then
getPhotos(searchTerm: String, completion: { _ in
// The code inside this block will be executed when getPhotos finishes
})
However, you may not get the effect that you expect, as the return in textFieldShouldReturn will probably be executed before the completion.
Can I ask what are you trying to do with this?
EDITED:
If what you want is to be sure pics have been loaded before dismissing the textfield, you can add a flag/bool in your viewController
var didLoadPics = false
and then when you add this line to your method
self.searchPhotos = results.searchResults
didLoadPics = true
so your textFieldShouldReturn would only need
return didLoadPics
I am trying to set up a tableview that refreshes user data after a button is pressed. RXSwift is used for the entire chain of events. Moya is used for routing.
I am trying to use the standard error handling given by Moya, which is:
provider.rx.request(.userProfile("ashfurrow")).subscribe { event in
switch event {
case let .success(response):
image = UIImage(data: response.data)
case let .error(error):
print(error)
}
}
The only way I have been able to get this to work, is to use an inner subscribe method. Please see code below. Can anyone think of a way that does not require an inner subscribe? It seems a bit clumsy as is.
class ViewController: UIViewController {
#IBOutlet weak var refreshBtn: UIButton!
#IBOutlet weak var tableView: UITableView!
let provider = MoyaProvider<MyAPI>()
let disposeBag = DisposeBag()
var latestUsers = Variable<[User]>([])
override func viewDidLoad() {
super.viewDidLoad()
setupObservableBtnRefreshWithDataFetch()
bindDataToTableView()
}
func setupObservableBtnRefreshWithDataFetch() {
let refreshStream = refreshBtn.rx.tap.startWith(())
let responseStream = refreshStream.flatMapLatest { _ -> SharedSequence<DriverSharingStrategy, [User]> in
let request = self.provider.rx.request(.showUsers)
// Inner Subscribe here, to be able to use the standard Moya subscribe methods for error handling
request.subscribe { event in
switch event {
case .success(let user):
print("Success")
case .error(let error):
print("Error occurred: \(error.localizedDescription)")
}
}
return request
.filterSuccessfulStatusCodes()
.map([User].self)
.asDriver(onErrorJustReturn: [])
}
let nilOnRefreshTapStream: Observable<[User]> = refreshBtn.rx.tap.map { _ in return [] }
let tableDisplayStream = Observable.of(responseStream, nilOnRefreshTapStream)
.merge()
.startWith([])
tableDisplayStream
.subscribe { event in
switch event {
case .next(let users):
print("Users are:")
print(users)
self.latestUsers.value = users
break
case .completed:
break
case .error(let error):
print("Error occurred: \(error.localizedDescription)")
break
}
}
.disposed(by: self.disposeBag)
}
func bindDataToTableView() {
latestUsers.asObservable()
.bind(to: tableView.rx.items(cellIdentifier: "cell", cellType: UITableViewCell.self)) { (_, model: User, cell: UITableViewCell) in
cell.textLabel?.text = model.login
}
.disposed(by: disposeBag)
}
}
class User: Decodable {
var name: String?
var mobile: Int?
var userRequestedTime: String?
var login: String?
init(name: String, mobile: Int, login: String = "") {
self.name = name
self.mobile = mobile
self.login = login
}
}
I have investigated Moya and learned it is a wrapper for network operations.
It's not entirely clear to what purpose the inner subscribe serves - based on my understanding, it triggers an identical but separate network request, which should not affect the other request subscription.
It also seems like the refreshButton tap emits two elements in tableDisplayStream (from responseStream (from refreshStream) and nilOnRefreshTapStream).
Note that Variable is deprecated. Personally, I also prefer .debug().subscribe() to manually printing events in the subscription closure.
Based on this, I would write the code as follows instead. I have not tested it. Hope it helps!
class ViewController: UIViewController {
// ...
private let provider = MoyaProvider<MyAPI>()
private let disposeBag = DisposeBag()
/// Variable<T> is deprecated; use BehaviorRelay instead
private let users = BehaviorRelay<[User]>(value: [])
private func setupObservableBtnRefreshWithDataFetch() {
refreshBtn.rx.tap
.startWith(()) // trigger initial load
.flatMapLatest { _ in
self.provider.rx.request(.showUsers)
.debug("moya request")
.filterSuccessfulStatusCodes()
.map([User].self)
.asDriver(onErrorJustReturn: []) // don't let the error escape
}
.drive(users)
.disposed(by: disposeBag)
}
private func bindDataToTableView() {
users
.asDriver()
.debug("driving table view ")
.drive(tableView.rx.items /* ... */)
.disposed(by: disposeBag)
}
}
I have two async function which send requests to my server.
DispatchQueue.global(qos: .userInitiated).async {
weak var weakself = self
self.unregisterPushServer(token: token!) { [weak self] success in
print("0")
if success {
print("1")
weakself?.unregisterPushInfoKeychain(token: token!)
print("2")
if let this = self {
print("PLEASE")
weakself?.registerPushServer(token: token!) { [weak this] success in
print("3")
if success {
print("4")
this?.registerPushInfoKeychain()
print("5")
}
}
}
print("success")
}
}
}
And the functions are
private func registerPushServer(token: String, completion: #escaping (Bool) -> ()) {
request() { (data, error) in
if data != nil {
completion(true)
} else {
completion(false)
}
}
private func unregisterPushServer(token: String, completion: #escaping (Bool) -> ()) {
request2() { (data, error) in
if data != nil {
completion(true)
} else {
completion(false)
}
}
But in console,
0
1
2
success
not seemed to executes codes after my PLEASE sign.
Why is my code is not working?
I first thought that the problem was about the queue, but it was not.
You don't need this line:
weak var weakself = self
By including [weak self] in the closure's capture list, self automatically becomes weak.
Try and replace the instances of weakself with just self.
I'm also thinking you may not even need the if let this = self condition.
I hope this helps.
OK, the problem was not in this code.
When I call this function, I did it like this.
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let pushService = PushService()
pushService.updateRegistrationStatus(token: fcmToken)
}
the
pushService.updateRegistrationStatus(token: fcmToken)
was the function which contains the code I asked above.
In this situation, the function updateRegistrationStatus is not guaranteed because pushService itself is released by ARC when messaging(...) function block is end.
class AppDelegate: UIResponder, UIApplicationDelegate {
let pushService = PushService()
...
func messaging(_ messaging: Messaging, didRefreshRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
self.pushService.updateRegistrationStatus(token: fcmToken)
}
}
Now the pushService object is not released because it is declared as a global variable.
I've written a function called 'configureLabels()' that is supposed to make a 'GET' request and retrieve a value which is then supposed to be set as the text for a label. The request is async so I thought I would be able to use an escaping closure to update the UI when the request is finished being made. I'm relatively new to coding, so I am not sure what I've done wrong. I'd really appreciate anyone's help in figuring this out.
This is the code containing the 'configureLabels()' method:
import UIKit
import SwiftyJSON
class ItemDetailViewController: UIViewController {
#IBOutlet weak var numberOfFiberGrams: UILabel!
var ndbnoResults = [JSON]()
var ndbno = ""
let requestManager = RequestManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
configureLabels()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func configureLabels() {
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
And here is the code containing the function that 'configureLabels()' calls:
func ndbnoRequest(ndbno: String, apiKey: String, completionHandler: #escaping (_ results: JSON?) -> Void) {
Alamofire.request("https://api.nal.usda.gov/ndb/V2/reports?ndbno=\(ndbno)&type=f&format=json&api_key=\(apiKey)", method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
completionHandler(json)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print(error)
}
}
}
Your code looks ok only issue I have find with your code is you are not calling the completionHandler in failure part, You need to always call completion block so it will gave you idea have you got response or not as of your completionHandler argument is type of [JSON] as of your not having response in failure part you are not calling completionHandler in it. What you can do is make it optional and call completionHandler with nil argument in case of failure.
func ndbnoRequest(ndbno: String, completionHandler: #escaping (_ results: [JSON]?) -> Void) {
let parameters = ["api_key": "tIgopGnvNSP7YJOQ17lGVwazeYI1TVhXNBA2Et9W", "format": "json", "ndbno": "\(ndbno)"]
Alamofire.request("https://api.nal.usda.gov/ndb/reports/V2", method: .get, parameters: parameters).responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
let ndbnoResults = json["foods"].arrayValue
completionHandler(ndbnoResults)
print("successful ndbno request")
case .failure(let error):
completionHandler(nil)
print("error with ndbno request")
}
}
}
Now call it this way and wrapped the optional in completion block so you can confirm you get response.
requestManager.ndbnoRequest(ndbno: ndbno) { (results) in
if let result = results {
let json = JSON(result)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
else {
print("Problem to get response")
}
}
Everything related to UI must be ALWAYS done on the main thread.
So try this:
DispatchQueue.main.async {
let json = JSON(results)
let fiber = json["food"]["nutrients"][7].dictionaryValue
for (key, value) in fiber {
if key == "value" {
self.numberOfFiberGrams.text = "\(value.stringValue)"
} else {
self.numberOfFiberGrams.text = "Fail"
}
}
}
P.S. I agree with Nirav about failure callback - you should handle it too. And I strongly recommend you to give functions and vars more readable and meaningful names, not "ndbnoRequest" and "ndbno". You won't remember what does it mean in few weeks :)