UINotification selecting from handleActionWithIdentifier display specific viewController - ios

I am new to iOS programming and I only understand swift language for now. For my app, I have set up user, local and remote notifications. The local notifications have 2 actions, 1 to dismiss and the other is to direct the user to a specific viewController.
My viewController hierarchy to the specific viewController I want to display is tabBarController -> (2nd tab) tableViewController -> (one of the many tablecells) viewController
After Richard's suggestion, I have simplified my code with the above hierarchy. I have performed a background fetch to my server in response to a remote push notification and save content locally before creating a local notification to inform the user. I have managed to set up to the point where I guide the user to the correct Tab on the tabViewController using self.window?.rootViewController.selectedIndex = 1 within application:handleActionWithIdentifier in App delegate.
How do I proceed with selecting the correct tablecell assuming that the local notification holds an index that can be used?
Edit:
After some tinkering, I managed to get the viewController I wanted to be displayed.
if var tabb = self.window?.rootViewController as? UITabBarController {
tabb.selectedIndex = 1
var controllers = tabb.childViewControllers
for controller in controllers {
if let navcon = controller as? UINavigationController {
if let tblvc = navcon.childViewControllers.first as? QuestionnaireTableViewController {
tblvc.becomeFirstResponder()
tblvc.moveUserToQuestionnaire(questionID)
}
}
}
}
func moveUserToQuestionnaire(questionID : String) {
var list = questionnaireObj.getQuestionnaireListData()
for item in list {
var selected = item["questionnaire_id"] as! NSString as String
if selected == questionID {
selectedUserID = item["study_id"] as! String
selectedReplyBatchID = item["reply_batch_id"] as! NSNumber
selectedQuestionnaireID = questionID
performSegueWithIdentifier("SelectQuestionnaire",sender:self)
break
}
}
}
If this is a correct method to use, please inform me. Thanks! Hope it also helps someone in the process. =)

Related

iOS Swift doesn't pass data back via delegation

I'm segueing from one view controller to another like so:
switch popIndex {
case 301:
let destVC = segue.destination as! IDPopoverViewController
destVC.popIndex = popIndex
destVC.titleString = "Issuer or Category"
...
In the second VC, the user enters data (String). I'm passing this data back to the originating VC via delegation:
case 301:
newIssuerDelegate?.appendName(name: dataToReturn)
print("In delegate call, dataToReturn = \(dataToReturn)")
...
The delegate function:
func appendName(name:String) {
newIssuer.name = name
issuerNameLabel.text = name
print("In appendName, name = \(name)")
saveIt()
}
The Protocol
protocol NewIssuerProtocol {
func appendName(name:String)
...
The Problem:
The data seemingly never arrives at the delegate function. The data doesn't appear on issuerNameLabel, nor in the print statement. Doesn't crash, just returns to the originating VC and idles, awaiting another command.
I've placed breakpoints in the delegate call (stops, as expected), and in the delegate function. the receipt of the data isn't acknowledged by the breakpoint, nor by the print statement. I should mention that none of the (6) other delegate functions in the same protocol appear to receive or display the relevant data, either. However, several similarly constructed delegation schemes throughout the app are working just fine.
My Plea
Can someone spot what I'm doing wrong on this one?
All help much appreciated!
Fix
I neglected to set the delegate when preparing for the segue, as was generously pointed out below by #vadian!
case 301:
let destVC = segue.destination as! IDPopoverViewController
destVC.popIndex = popIndex
destVC.titleString = "Issuer or Category"
destVC.newIssuerDelegate = self

Push View Controller on Remote Notification's 'Default User Action' in Xamarin iOS

I have setup Remote Notifications and added a few custom keys to the payload that will give the option to push a specific detail ViewController on the home page if the user taps the notification and wants to read more about that topic.
For example: linkType: "services" and linkPost: "main" would tell my app to push the main services page when the app comes to the foreground.
I can successfully access the keys and values in my variables topicValue, linkType, and linkPost but I'm having trouble debugging the path the code takes when the user taps the notification so I know where and when to push the correct ViewController. I'm using the Xamarin.Essentials packages so I can Get and Set these key value pairs. I can access them anywhere throughout the app.
public class MyNotificationCenterDelegate : UNUserNotificationCenterDelegate
{
public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
if (response.IsDefaultAction)
{
var userInfo = response.Notification.Request.Content.UserInfo;
string topicValue = userInfo.ObjectForKey(new NSString("topic")).ToString();
string linkType = userInfo.ObjectForKey(new NSString("linktype")).ToString();
string linkPost = userInfo.ObjectForKey(new NSString("linkpage")).ToString();
Preferences.Set("linkType", linkType);
Preferences.Set("linkPost", linkPost);
I originally tried using the launchOptions parameter within the FinishedLaunching method in the AppDelegate but when I set breakpoints they don't seem to hit when I tap the remote notification. Any help accomplishing this in Xamarin.iOS would be greatly appreciated.
Two things (Background state or InActive state)
If your app is not available in the background and you work with notification then you should use DidFinishLaunching
For this situation, you should set the key of which is comes from the notification. And you will get that key from launchOptions.
Compare that key in viewWillAppear of HomeViewController and put RedirectScreen code if key match.
Ex:- linkType: "services"
If your app is available in the background and notification comes then DidReceiveNotificationResponse call and you can redirect any of the screens in your app.
For this first, you should fetch topMostViewController then use this controller for the redirect to any screen.
Ex:- To get topMostViewController
public static UIViewController GetTopViewController()
{
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
vc = vc.PresentedViewController;
if (vc is UINavigationController navController)
vc = navController.ViewControllers.Last();
return vc;
}
This was the solution to my particular use case. Thank you again Nilesh for helping me arrive here :)
public class MyNotificationCenterDelegate : UNUserNotificationCenterDelegate
{
UIViewController detailViewController;
public override void DidReceiveNotificationResponse(UNUserNotificationCenter center, UNNotificationResponse response, Action completionHandler)
{
UIApplication application = UIApplication.SharedApplication;
if (response.IsDefaultAction)
{
var userInfo = response.Notification.Request.Content.UserInfo;
string linkType = userInfo.ObjectForKey(new NSString("linktype")).ToString();
string linkPost = userInfo.ObjectForKey(new NSString("linkpage")).ToString();
//I'm using Xamarin.Essentials package to manage my User Defaults Key Value pairs with the
// Preferences method. Then I can see if those keys are set or not in ViewDidLoad in
// my HomeViewController. This covers the case if the app had been closed
//when the notification is tapped.
Preferences.Set("linkType", linkType);
Preferences.Set("linkPost", linkPost);
var window = UIApplication.SharedApplication.KeyWindow;
var tc = window.RootViewController as UITabBarController;
if(tc != null)
{
//this is essential. if app was in background then `tc != null` and this logic can run here.
//If the app was closed and `tc == null` then this logic will be carried out at the end of my
// ViewDidLoad method in my HomeViewController instead of this method.
// carry out your logic for your app to either push a view controller or
// select a TabBarController index ...
//if you are pushing a detail view controller on your Home Navigation Controller
var homeNavController = tc.ChildViewControllers[0] as UINavigationController;
homeNavController.PushViewController(detailViewController, true);
tc.SelectedIndex = 0; //select my HomeViewController via TabBarController index
//HomeViewController is where I'm pushing the detail controller so I want to make to navigate in case the user backgrounded the app on another TabBarController index.
}
}
completionHandler();
}
So in addition to this I needed to carry out the same navigation logic (choosing which view controller to push or which TabBarController index to select) at the end of my ViewDidLoad method in my HomeViewController. You access the Preferences keys you set to choose where to navigate. This covers the case of the app being closed when the user taps on the notification.
Hope this helps somebody else trying to accomplish this type of 'deep linking' with their remote notification in Xamarin.iOS!

Display a particular user and all their information stored on parse on a different View Controller in Swift

In my app each user sets up a profile page and all this info is stored on Parse. When the current user hits a button it segues to a tableViewController that displays in cells all the other users on the app. In each cell there is a button which the user presses to view the full profile page of the user that they have selected. I have a VC set up to hold all the info I just don't know how to display the particular user that the current user has selected into it. How do I single out the chosen users information? I have read the Parse documentation on relational queries but I can't seem to get my head around it. https://parse.com/docs/ios/guide#queries-relational-queries
So in finer detail basically all my users are displaying on a VC. It has name and profile picture, when the user clicks on the info button beside a certain user it should open a new screen and display all of this users information stored on Parse.
I have tried creating a static outside the class
struct Data { var OtherName:String! var id:String! }
And then in my ViewDidLoad:
let query = PFQuery(className: "_User")
// otherQuery.orderByDescending("createdAt")
query.whereKey("username", notEqualTo:PFUser.currentUser()!.username!)
query.findObjectsInBackgroundWithBlock { (users: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// success
print(users!.count)
for user in users! {
self.imageFiles.append(user["image"] as! PFFile)
self.instrumentText.append(user["instrument"] as! String)
self.nameText.append(user["name"] as! String)
self.ageText.append(user["age"] as! String)
// self.locationText.append(user["location"] as! PFGeoPoint)
var singleData = Data()
singleData.id = user.objectId
singleData.OtherName = user["name"] as! String
print("\(singleData)")
} // users
Then on the VC that the info button segues to I am trying to call it and upload each label with just the user that was selected.
self.userName.text = "" as String
But the "" is staying blank.
Then I was thinking maybe if I add that when the more info button is pressed that the user it was pressed on gets chosen and is added to a chosen string on Parse and then I just have to call the user from the chosen string when I am on the chosen users profile page and call the info that way and then when the user clicks the back button the user is no longer chosen. It just seems like I would be adding and taking away a lot of users to "chosen" in Parse is there a better way?
I am able to display all the users info but how do I just display a chosen user from Parse without using the objectId because at the top of each cell it will return a different user here each time depending on settings previously set on a different VC such as only show users in a certain age bracket etc. I know I must set an Id of some sort on button touched but I am at a complete loss. The only online help out there that I can find is set for other languages eg. https://parse.com/docs/js/guide#users-querying>
So I guess my question is :
1.Does anyone know a tutorial they could point me towards.
2.Does anyone know how I even get started and I can go from there myself.
3.Can anyone help?
*Swift and Parse newbie little to no experience in other languages.
New code in firstVC:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "userProfileDetailsSegue" {
if let indexPath = resultsPageTableView.indexPathForSelectedRow {
let controller = (segue.destinationViewController) as! UserProfileDetailsViewController
***error here: UserProfileDetailsViewController.userToShowDetail = indexPath.row
//line above is the one I can't get to work it needs to equal to the cell selected?
}
}
}
New Code in destinationVC:
var userToShowDetail: PFUser? {
didSet {
self.configureView()
}
}
func configureView() {
// Update the user interface for the detail item.
if let userToShowDetail: PFUser = self.userToShowDetail {
if let label = self.userName {
nameText.append(userToShowDetail["name"] as! String)
// self.nameText.append(user["name"] as! String)
}
}
}
You're going to need to do a couple of things for this to work. First, in the view controller that you're using to show another user's information, you need to set a property like var userToShowDetail: PFUser?. Then in your view with all of the users of your app, you need to create a segue to bring them from the current view to the detail view. Assuming you're using a TableView, you can connect just one prototype cell to the detail view you're trying to navigate. Then you'll need to override prepareForSegue and set the new view controller's user equal to the user at the row that was selected. I don't know how you're storing your objects of user's of your app, but you should probably be able to do something like this.
override fun prepareForSegue(segue: UIStoryboardSegue) {
if segue.identifer == "segueToShowDetailInfo" {
//get the index path for the row that was selected
let indexPath = self.tableView.indexPathForSelectedRow()
//get the PFUser object for that particular row
let userToShow = self.appUsers[indexPath.row]
//create your new view controller
let newVc = segue.destinationViewController as! DestinationVC
//assign the new vc's property to your object
newVc.userToShowDetail = userToShow
}
}
But in your query, you probably shouldn't have a separate array of the the different parts of the user's data. You should just make one array, call it var appUsers = [PFUser]() and then when you get a user back in your query, just say:
for user in users! {
self.appUsers.append(user) as! PFUser
}
There, now you have a whole array of objects that holds a reference to every user. When someone is using your app, and selects a cell, then you can perform the prepare for segue method as above.
Note that I also noticed you have a didSet on your variable in your new view controller. Calling a function to update the UI after this variable is set will not work, and may crash, because the view controller doesn't have a window yet.

Persist data and pass to multiple views

I have an app where a user logs in by entering their details and that sends a HTTP GET request to my API which authenticates the user and sends the users data/user object back from the database.
Once this is done, within my HTTP request which is triggered on button tap I send the users data onto the next view and perform a transition to the view by using the following code:
if let parseJSON = json
{
// parseJSON contains the return data. We are programmatically creating a segue between two views
// passing data between them.
dispatch_async(dispatch_get_main_queue())
{
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("mainView") as! MainViewController
var fullname = parseJSON["employeeName"] as! String
var job = parseJSON["jobTitle"] as! String
var email = parseJSON["email"] as! String
vc.username = fullname
vc.jobTitle = job
vc.userEmail = email
self.presentViewController(vc, animated: true, completion: nil)
}
The above works perfectly.The from my second view I create a prepare for segue for another view where I am trying to pass the user data that was just passed to the 2nd view when the user logged in by using the code below:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if (segue.identifier == "settings") {
// pass data to next view
let viewController = segue.destinationViewController as! SettingsTableViewController
viewController.username = username
viewController.jobTitle = jobTitle
}
}
This again works properly. When I tap on the button to go to view 3 the prepareforsegue function passes the data and shows the following view (ignore email field):
But when I click on the back button and try to access to the same view all the data thats coming from the API and is being passed from View 1 to View 2 to View 3 disappears. See below:
I DO understand the reason why this happening, I am passing data in 1 direction and it is not being saved only being passed from one view to another and when I go back a view that process of passing between breaks and hence the data is lost.
My question is how can I preserve data when the user first logs in and the API sends the data back. Can I save it? and keep passing it through multiple views and still keep it no matter what?
Any help will be greatly appreciated.
Thanks in advance.
In short : Store the data you get in an instance variable in your view and use that variable for the segues.
Long Explanation:
Best practice is to create a model class for the stuff you're getting from your API, put all data in a variable of that class when retrieving the info, and like a said have a variable of that type in your view classes.
Tip: read a bit about the MVC paradigma's (lotsa stuff online, if you have some time read the book Design Patterns by the gang of four)

Swift - Passing data between view controllers

I am new to mobile development, and I am building an iOS application with Swift that involves a process where a user answers a series of questions in the form of multiple view controllers in a navigation controller stack.
Basically, I need to capture the text from all the buttons that the user clicks on in his process from controller A to controller D. (One button click leads to the next view controller) By the time, the user reaches controller D, I want to have a array of data that represents what he pressed from controllers A to C.
On controller D, I ask the user if they would like to repeat the process. For example, I would ask the user about a car they own, and, if they own another car, D would take them back to controller A and repeat the process again where the new responses would be saved as another array.
After the user has no more cars, the program moves on to the next section where I would need to load these arrays from before and display different view controllers based on what they entered.
What would be the best way to handle this? I have considered using SQlite to temporarily save the data, but other people have told me to use NSUserDefaults since it takes less time. I will NOT need persistence for this application since I want the user to restart the process from the beginning after they quit the application.
to store a value in one of the viewcontrollers:
let userDefaults = NSUserDefaults.standardUserDefaults()
// user tapped on button 3 (value 2, because 4 buttons => indices 0-3)
userDefaults.setInteger(2, forKey: "Answer1")
userDefaults.synchronize()
to load the values after answering all the questions:
let userDefaults = NSUserDefaults.standardUserDefaults()
let answer1 = userDefaults.integerForKey("Answer1")
let answer2 = userDefaults.integerForKey("Answer2")
...
userDefaults.synchronize()
to delete the saved stuff on application launch (in applicationdidfinishlaunching):
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.removeObjectForKey("Answer1")
userDefaults.removeObjectForKey("Answer2")
...
userDefaults.synchronize()
good luck! :)
I think the path from controller A to controller D can be called a Session for your user.
What you need is a way to store information about this session outside any controller, and this session to be accessible from any controller.
You should create a class Session (sorry, example in objective-C not in Swift):
Session.h
#interface Session : NSObject
- (void)addValue:(id)value;
- (NSArray *)allValues;
- (NSUInteger)valueCount;
- (void)reset;
#end
Session.m
#interface Session ()
{
NSMutableArray *_values;
}
#end
#implementation Session
- (instancetype)init
{
self = [super init];
if( !self ) return nil;
_values = [[NSMutableArray alloc] init];
return self;
}
- (void)addValue:(id)value
{
[_values addObject:value];
}
- (NSUInteger)valueCount
{
return _values.count;
}
- (NSArray *)allValues
{
// NSArray to have a immutable array of values
return [NSArray arrayWithArray:_values];
}
- (void)reset
{
[_values removeAllObjects];
}
#end
This way, you can create a globally accessible Session object (Session *s = [[Session alloc] init];), and use it throughout all your controllers.
Edit: Learned a lot since my original answer.
What I do (this may not be the only way, but works for me), is to instantiate a class of the kind I need on the main View Controller, as well as on each View Controller that modifies the data. If I need to update information that refers to that class, I pass the object in the segue (Swift 3):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case constants.aSegue: // not the real name of my segue :-P
// grab inside the navcon
if let navCon = segue.destination as? UINavigationController {
if let destinationVC = navCon.viewControllers.first as? CGSettingsPopupViewController {
destinationVC.remoteObject = localObject
// localObject is a kind of the class I need, as is remoteObject
// now, changes to destinationVC.remoteObject will be accessible locally, when the destinationVC returns control
}
}
default:
break
}
}
}
Since they are passed by reference, changing data members in 'remoteObject' also changes the ones in 'localObject' since they refer to the same object.

Resources