Hi I'm currently working on a project for a customer where i need to access the phones contacts.
I managed to ask for the Permission to access the contacts and i m handling the two different states (granted, denied).
Apparently the customer wants the following workflow:
hit an add button
ask for permission
granted:
performs a segue to a tablewview with all contacts listed
denied: performs a segue to a differnt view and keeps asking on the inital button hit to grant access to the contacs
I ve managed the complete flow and fetched the complete data. No i m stuck with two problems:
I can't get the the ask for permission alertview pop up again (from
my understanding the user needs to set this in the Application
Settings ->App privacy settings). Is this correct?
It appears that if access is granted for the first time and i
perform a segue the tableview is empty because the data array is nil
(i can't figure out why).
- (void)addButtonTouched {
[self.addressBook accessAddressBook];
[self.activityView startActivityViewWithMessage:#"Wait a second"];
if (access) {
self.contactsArray = [self.addressBook getAllContacts];
if (self.contactsArray.count != 0) {
[self performSegueWithIdentifier:#"addEntrySegue" sender:self];
} else {
[self performSegueWithIdentifier:#"noContactsSegue" sender:self];
}
}
Am I pushing to soon to the next ViewController to fill self.contactsArray?
My other approach was to send a Notifictaion to my rootViewController when the access was granted and then perform the segue. This was the closest result i could get, but the ViewController push delayed aber 8-10 seconds.
> - (void)receivedNotification:(NSNotification *)notification {
> if (access) {
> self.contactsArray = [self.addressBook getAllContacts];
> if (self.contactsArray.count != 0) {
> [self performSegueWithIdentifier:#"addEntryrSegue" sender:self];
> } else {
> [self performSegueWithIdentifier:#"noContactsSegue" sender:self];
> }
> }
> }
Thanks in advance. I hope i got this explained well enough.
Yes. The user has to re-enable it manually from the setting. Your best bet may be to create the alternate view with instructions on how to accept it.
The array is filled with all the contacts. You want to make sure that you do two things. One: reloadData() on the table. Two: make sure you handle asynchronous operation correctly. So, the best way to handle this is running the code like this:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
// Fill array with contacts here. Run the function. Whatever you need to do goes here.
dispatch_sync(dispatch_get_main_queue(), ^{
// Reload the table. Whatever UI changes you want go here.
});
});
Related
I am using SendPayment intent for payment domain app. Basically, it shows two screens:-
1) Send Mooney
2) Money sent
Since the same intent view controller is shown for both the flows, can anybody share some tips how change the "Send Money" intent View by "Money Sent" view.
Also, in apple documentation, its written to use childViewController, but wondering on what basis it has to be used as in configure method, intenthandlingstatus is "undefined" always.
func configure(with interaction: INInteraction!, context: INUIHostedViewContext, completion: ((CGSize) -> Void)!) {
// here interaction.intentHandlingStatus allways shows undefined
}
Please suggest.
Thanks
I haven't found any variables that are set by Apple to be able to tell the difference between the confirm step and the send step.
However if you structure your data correctly, there is a way to indirectly get this working.
Apple requires you to setup a PaymentRecord and attach it to each IntentResponse in confirmSendPayment:completion and handleSendPayment:completion:
What I am doing is I set the payment status to Pending in the confirm step and then to Completed in the handle step. So I can use the following code in the UI extension to show the proper UI for what step I am on:
INSendPaymentIntent *sendPaymentIntent = (INSendPaymentIntent *)interaction.intent;
INSendPaymentIntentResponse *sendPaymentResponse = (INSendPaymentIntentResponse *)interaction.intentResponse;
if (sendPaymentResponse.paymentRecord.status == INPaymentStatusPending) {
// Confirm step
[self setupUI:sendPaymentIntent forView:self.previewView];
self.previewView.hidden = false;
self.completeView.hidden = true;
} else if (sendPaymentResponse.paymentRecord.status == INPaymentStatusCompleted) {
// Action performed step
[self setupUI:sendPaymentIntent forView:self.completeView];
self.previewView.hidden = true;
self.completeView.hidden = false;
}
This question is extension to my previous question with new requirements. This is My Previous question.
So My New Requirement is :
Now If I want to delete group chat how I should handle this ? If I use the same method inside it we are passing forAllUsers as "NO" which is hard coded. written inside QMChatServices.m
- (void)deleteDialogWithID:(NSString *)dialogId completion:(void (^)(QBResponse *))completion {
NSParameterAssert(dialogId);
__weak __typeof(self)weakSelf = self;
[QBRequest deleteDialogsWithIDs:[NSSet setWithObject:dialogId] forAllUsers:NO successBlock:^(QBResponse *response, NSArray *deletedObjectsIDs, NSArray *notFoundObjectsIDs, NSArray *wrongPermissionsObjectsIDs) {
//
[weakSelf.dialogsMemoryStorage deleteChatDialogWithID:dialogId];
[weakSelf.messagesMemoryStorage deleteMessagesWithDialogID:dialogId];
if ([weakSelf.multicastDelegate respondsToSelector:#selector(chatService:didDeleteChatDialogWithIDFromMemoryStorage:)]) {
[weakSelf.multicastDelegate chatService:weakSelf didDeleteChatDialogWithIDFromMemoryStorage:dialogId];
}
[weakSelf.loadedAllMessages removeObjectsForKeys:deletedObjectsIDs];
if (completion) {
completion(response);
}
} errorBlock:^(QBResponse *response) {
//
if (response.status == QBResponseStatusCodeNotFound || response.status == 403) {
[weakSelf.dialogsMemoryStorage deleteChatDialogWithID:dialogId];
if ([weakSelf.multicastDelegate respondsToSelector:#selector(chatService:didDeleteChatDialogWithIDFromMemoryStorage:)]) {
[weakSelf.multicastDelegate chatService:weakSelf didDeleteChatDialogWithIDFromMemoryStorage:dialogId];
}
}
else {
[weakSelf.serviceManager handleErrorResponse:response];
}
if (completion) {
completion(response);
}
}];
}
So Now my doubt is..
Question 1 : What if we want to delete dialog for all the users. Question 2 : Lets say there are 3 users. User1 , User2 and User3. Now User1 has created group with User2 and User3.
So how this method is useful for all the different 3 users. I mean What happens if User1 uses
[ServicesManager.instance.chatService deleteDialogWithID:dialog.ID completion:nil];
and what happens if User2 and User3 uses the same method.
Weather it works as exit from the dialog or deleting dialog. I'm little confused how this method works for different users in case of group and public chat.
Question 3 : Is there any other way to exit from the group chat ? I hope it is clear !!
You right, there is no interface for deleting dialog for all users right now. But keep in mind that only the owner of group dialog (its creator) can delete it. And owner cannot be changed, even if he will left the dialog. We will consider adding such method in a near future, but for now you can easily modify it for your needs (you are welcome to fork from our services repository).
If any of the user uses this method - he will be out of that group (by server), but group itself will still exist with other members. But other members will only know about left users when they will redownload dialog from REST. So in order to notify them live, we are sending XMPP notifications before leaving, like this one
Pretty much what I said in 2. Notifying users live about our leaving and deleting dialog through REST (QBRequest) request.
What is the best practice for pushing a view controller based on the feedback from the requesting Current Location iOS-based dialog (shown in the image below)?
I am trying to determine after the selections is made to either success -> send the user onward in the flow OR fail -> show a screen that requires they allow current location.
I've gotten as far as calling this method from a method where a button-press allocates CLLocationManager:
- (void) confirmInfo {
BOOL locationAllowed = [CLLocationManager locationServicesEnabled];
if (locationAllowed==NO) {
// Show the how-to viewcontroller
} else {
// Go to the next step onboarding
}
}
But I do not know how to wait until the input comes back from the dialog to choose which VC to show the user.
You can have Delegate method user will allow or even if user dont allow.. and from that response you can continue you push pop.
Else even is you can try block based structure to maintain your flow.
following link may help you..
https://github.com/intuit/LocationManager
or
https://github.com/FuerteInternational/FTLocationManager
INTULocationManager *locMgr = [INTULocationManager sharedInstance];
[locMgr requestLocationWithDesiredAccuracy:INTULocationAccuracyCity
timeout:10.0
delayUntilAuthorized:YES // This parameter is optional, defaults to NO if omitted
block:^(CLLocation *currentLocation, INTULocationAccuracy achievedAccuracy, INTULocationStatus status) {
if (status == INTULocationStatusSuccess) {
// Request succeeded, meaning achievedAccuracy is at least the requested accuracy, and
// currentLocation contains the device's current location.
}
else if (status == INTULocationStatusTimedOut) {
// Wasn't able to locate the user with the requested accuracy within the timeout interval.
// However, currentLocation contains the best location available (if any) as of right now,
// and achievedAccuracy has info on the accuracy/recency of the location in currentLocation.
}
else {
// An error occurred, more info is available by looking at the specific status returned.
}
}];
First, create a CLLocationManager, then set your controller to its delegate, finally call [self.locationManager startUpdatingLocations]; to display the dialog.
Then use the CLLocationManagerDelegate methods to determine what the user chose:
On success,
– locationManager:didUpdateLocations:
gets called. Otherwise
– locationManager:didFailWithError:
I'm new to iOS development and trying to solve following problem.
In my app (which speaks with REST API) I want to make initial request to server on app start to get user info. I decided to use separate service class with singleton method. It makes request to server once and then returns user instance.
#implementation LSSharedUser
+ (LSUser *)getUser {
// make request to api server on the first call
// on other calls return initialized user
static LSUser *_sharedUser = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
LSHTTPClient *api = [LSHTTPClient create];
[api getUser:^(AFHTTPRequestOperation *operation, id user) {
_sharedUser = [[LSUser alloc] initWithDictionary:user];
} failure:nil];
});
return _sharedUser;
}
#end
My question is it a proper way of initializing global data from server? As you see request is async (with AFNetworking lib) so it will return null until request is finished.
Another problem here is that once it failed (bad connection for example) user will be null forever.
update your code like this
static LSUser *_sharedUser ;
+ (LSUser *)getUser {
// make request to api server on the first call
// on other calls return initialized user
if(_sharedUser){
//this will execute only at first time
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
LSHTTPClient *api = [LSHTTPClient create];
[api getUser:^(AFHTTPRequestOperation *operation, id user) {
_sharedUser = [[LSUser alloc] initWithDictionary:user];
return _sharedUser;
} failure:nil];
});
}
//this will execute 2nd time
return _sharedUser;
}
for answer to line ->
Ques 2 )Another problem here is that once it failed (bad connection for example) user will be null forever.
->once _sharedUser is initialized user will get _sharedData. but until shared data is not initialized it will return null whenever called.
Ques 1 )My question is it a proper way of initializing global data from server? As you see request is async (with AFNetworking lib) so it will return null until request is finished.
a better way is to implement your own custom delegate methods. once request is fetched or when call that do your work in that delegate method.
for 1st time: execute calling delegate methods when request is fetched or failed.
for 2nd time: after if block call delegate methods.
The basic approach requires an asynchronous design.
Say you have an asynchronous method:
- (void) loadUserWithCompletion:(void (^)(NSDictionary* user, NSError* error))completion;
You execute whatever you need to execute in the "Continuation" (the completion block):
[self loadUserWithCompletion:^(NSDictionary* params, NSError*error) {
if (user) {
User* user = [[User alloc] initWithDictionary:params];
// better we ensure we execute the following on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
self.model.user = user;
// If we utilize KVO, observers may listen on self.model.user
// which now get a notification.
// We also may to notify the user through an alert:
...
});
}
else {
// handler error
}
}];
With the asynchronous programming style you need to be more carefully when letting the user execute arbitrary tasks (say, tabbing buttons). You may consider to disable tasks which require a user model. Alternatively, display an alert sheet when the user model is not yet available.
Usually, you use some "observer" technique to get notified when the user model is eventually available. You may use KVO or completion handlers, or use some dedicated third party library which is specialized for those problems (e.g. a Promise library).
You should also let the user cancel that task at any point. This however requires a more elaborated approach, where you have "cancelable" tasks and where you hold references to these tasks in order to able to send them a cancel message.
This has been a hard one to search.
I found a similar question, iOS 5 Wait for delegate to finish before populating a table?, but the accepted answer was 'Refresh the table view,' and that does not help me. The other results I found tended to be in c#.
I have an app that streams from iPhone to Wowza servers. When the user hits record, I generate a unique device id, then send it to a PHP script on the server that returns a JSON document with configuration settings (which includes the rtmp dump link).
The problem is, the delegate methods are asynchronous, but I need to get the config settings before the next lines of code in my - (IBAction)recordButtonPressed method, since that code is what sets the profile settings, and then records based on those settings.
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
Is there not some simple waitUntilDelegateIsFinished:(BOOL)nonAsyncFlag flag I can send to the delegator so I can have sequential operations that pull data from the web?
I've realized I could make the NSURLConnection in -recordButtonPressed like I am currently, and then continue the setup code inside the delegate method connectionDidFinishLoading (or just encapsulate the setup and method call it from there) but that's sacrificing coherent design for functionality and that sucks.
You have analyzed and understood the situation and you have described its possible solutions perfectly. I just don't agree with your conclusions. This kind of thing happens all the time:
- (void) doPart1 {
// do something here that will eventually cause part2 to be called
}
- (void) doPart2 {
}
You can play various games with invocations to make this more elegant and universal, but my advice would be, don't fight the framework, as what you're describing is exactly the nature of being asynchronous. (And do not use a synchronous request on the main thread, since that blocks the main thread, which is a no-no.)
Indeed, in an event-driven framework, the very notion "wait until" is anathema.
Why not to use synchronous request?
Wrap your asynchronous NSURLConnection request in a helper method which has a completion block as a parameter:
-(void) asyncDoSomething:(void(^)(id result)completionHandler ;
This method should be implemented in the NSURLConnectionDelegate. For details see the example implementation and comments below.
Elsewhere, in your action method:
Set the completion handler. The block will dispatch further on the main thread, and then perform anything appropriate to update the table data, unless the result was an error, in which case you should display an alert.
- (IBAction) recordButtonPressed
{
[someController asyncConnectionRequst:^(id result){
if (![result isKindOfClass:[NSError class]]) {
dispatch_async(dispatch_get_main_queue(), ^{
// We are on the main thread!
someController.tableData = result;
});
}
}];
}
The Implementation of the method asyncConnectionRequst: could work as follows: take the block and hold it in an ivar. When it is appropriate call it with the correct parameter. However, having blocks as ivars or properties will increase the risk to inadvertently introduce circular references.
But, there is a better way: a wrapper block will be immediately dispatched to a suspended serial dispatch queue - which is hold as an ivar. Since the queue is suspended, they will not execute any blocks. Only until after the queue will be resumed, the block executes. You resume the queue in your connectionDidFinish: and connectionDidFailWithError: (see below):
In your NSURLConnectionDelegate:
-(void) asyncConnectionRequst:(void(^)(id result)completionHandler
{
// Setup and start the connection:
self.connection = ...
if (!self.connection) {
NSError* error = [[NSError alloc] initWithDomain:#"Me"
code:-1234
userInfo:#{NSLocalizedDescriptionKey: #"Could not create NSURLConnection"}];
completionHandler(error);
});
return;
}
dispatch_suspend(self.handlerQueue); // a serial dispatch queue, now suspended
dispatch_async(self.handlerQueue, ^{
completionHandler(self.result);
});
[self.connection start];
}
Then in the NSURLConnectionDelegate, dispatch a the handler and resume the
handler queue:
- (void) connectionDidFinishLoading:(NSURLConnection*)connection {
self.result = self.responseData;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
Likewise when an error occurred:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
self.result = error;
dispatch_resume(self.handlerQueue);
dispatch_release(_handlerQueue), _handlerQueue = NULL;
}
There are even better ways, which however involve a few more basic helper classes which deal with asynchronous architectures which at the end of the day make your async code look like it were synchronous:
-(void) doFourTasksInAChainWith:(id)input
{
// This runs completely asynchronous!
self.promise = [self asyncWith:input]
.then(^(id result1){return [self auth:result1]);}, nil)
.then(^(id result2){return [self fetch:result2];}, nil)
.then(^(id result3){return [self parse:result3];}, nil)
.then(^(id result){ self.tableView.data = result; return nil;}, ^id(NSError* error){ ... })
// later eventually, self.promise.get should contain the final result
}