I'm doing a very easy app for me. When I launch this app, it simply bring me to this web page https://social.tre.it/expert.
I would like to automatise login so is there a way to autofill username and password, check the "I accept condition" mark and push "login" button?
I tried this Autofill Username and Password UIWebView Swift but it doesn't work, I'm not the creator of the page so I don't know exactly how it's structured.
Would be cool even if iCloud keychains would fill the forms...but maybe I'm asking too much!
You can use JavaScript to do it.
If you inspect the page, you can pretty easily spot the IDs of the text input fields (expert_email and expert_password) and execute some JS code to fill them.
Implement the webViewDidFinishLoad method of the UIWebView delegate like so:
OBJECTIVE-C:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//fill data
NSString *email = #"myemail#email.com";
NSString *password = #"mypassword";
NSString *fillDataJsCall = [NSString stringWithFormat:#"document.getElementById('expert_email').value = '%#';document.getElementById('expert_password').value = '%#';", email, password];
[webView stringByEvaluatingJavaScriptFromString:fillDataJsCall];
//check checkboxes
[webView stringByEvaluatingJavaScriptFromString:#"document.getElementById('expert_remember_me').checked = true; document.getElementById('expert_terms_of_service').checked = true;"];
//submit form
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^(void)
{
[webView stringByEvaluatingJavaScriptFromString:#"document.forms[\"new_expert\"].submit();"];
});
}
SWIFT:
func webViewDidFinishLoad(webView: UIWebView) {
// fill data
let savedUsername = "USERNAME"
let savedPassword = "PASSWORD"
let fillForm = String(format: "document.getElementById('expert_email').value = '\(savedUsername)';document.getElementById('expert_password').value = '\(savedPassword)';")
webView.stringByEvaluatingJavaScriptFromString(fillForm)
//check checkboxes
webView.stringByEvaluatingJavaScriptFromString("document.getElementById('expert_remember_me').checked = true; document.getElementById('expert_terms_of_service').checked = true;")
//submit form
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * NSEC_PER_SEC)), dispatch_get_main_queue()){
webView.stringByEvaluatingJavaScriptFromString("document.forms[\"new_expert\"].submit();")
}
}
This should fill your info. However, this code can easily break if this website changes its structure in the future. This method of executing JS code is more reasonable when you're handling your own pages.
Edit:
I updated the code to demonstrate how to check the checkboxes and how to submit the form.
Some notes:
when the login fails the page is reloaded, so you're going to end up with an endless loop of calls to webViewDidFinishLoad since you're trying to submit over and over again, so you might want to add some logic to break in this case. That's why I put a delay before submitting in order to be able to see what's going on.
In addition to the info in the previous point - you're going to get calls to webViewDidFinishLoad anyway after login is successful (when redirected to the next page) so you might want to raise a flag once a different page loads (perform the login attempt only when on the login page).
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;
}
I am working on ios app,,User will login to the app.I have multiple screens in one screen i have a requirement is if user first time visits that screen i need to show the instructions screen.If screen visits count more than once no need to show the instructions.
I need to store that screens count for each user separately how can we achieve this in appcelerator titanium
Please help me thanks in advance.
You could use the open or focus event to pick up when someone hits the screen and then either:
If this is a connected app, and you're downloading the user profile on login, you could store a flag to say tutorialSeen or similar. Check this when the window opens / focuses or
if not connected or you want to store locally, store the flag in the Ti.App.Properties under a user profile.
(you could equally store a count value that increments when they hit the screen but since you just want the first time captured, I'd go with a) capture event, b) check property, c) if false, set to true and show tutorial, d) if true, skip.)
Hope that helps!
I think you do not need to store the user visit count if your only purpose is to show the Instruction window only when user login into the app for first time.
You can do it this way:
someController.js
function onLoginSuccess() {
// getInt() will return the value of INSTRUCTION_WINDOW property, you can name this property whatever you want.
// if this property does not exist, then it will return the value of the second parameter.
var showInstruction = Ti.App.Properties.getInt("INSTRUCTION_WINDOW", 1);
if (showInstruction) {
Alloy.createController('instructionWindow').getView().open();
} else {
Alloy.createController('nextWindow').getView().open();
}
}
// logout function can be anywhere
// remember to set the property to 1 if you want to show the instructions again after logout and then login.
function logout() {
Ti.App.Properties.setInt("INSTRUCTION_WINDOW", 1);
}
instructionWindow.js
$.instructionWindow.addEventListener('open', function () {
// after opening the instruction window, set this property to 0
// so it won't be shown up the next time the already logged-in user opens the app
Ti.App.Properties.setInt("INSTRUCTION_WINDOW", 0);
});
first you set the nsuserdefaults value #"1".
[[NSUserDefaults standardUserDefaults] setObject:#"1" forKey:#"showtheinstruction"];
[[NSUserDefaults standardUserDefaults] synchronize];
after this you have to compare this string and shows the instruction message.
NSString *str_savedValue = [[NSUserDefaults standardUserDefaults]
stringForKey:#"showtheinstruction"];
if (str_savedValue isEqualToString:#"1") {
// show the instruction window.
}
else
{
// don't show the instruction window.
}
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.
});
});
I am using the standard iOS Google Drive SDK login mechanism with scope kGTLAuthScopeDrive (or "https://www.googleapis.com/auth/drive"). During login, after providing the user name and password, the view window shows:
This app would like to:
(1) Know who you are on Google
(2) View your email address
(3) View and manage the files and documents in your Google Drive
Some other text.
Cancel Button Accept Button
The two buttons fall off the bottom of an iPhone 5 screen and need to be scrolled up to be seen and tapped. I don't need to know "who you are on Google" or "your email address". Is there a scope I can use that only manages Google Drive files so that the Accept button will appear on the initial view without needing to scroll up? Or, is there some other way to auto-scroll to show the buttons?
Unfortunately there is not a scope for what you are looking for. If you look at the Drive 'About' request, you'll see why it results in the first 2 notes. There is not a scope that allows you to manage files and not see this information.
"kind": "drive#user",
"displayName": string,
"picture": {
"url": string
},
"isAuthenticatedUser": boolean,
"permissionId": string,
"emailAddress": string
Commenting out line 142 in GoogleAPI file "GTMOAuth2SignIn.m" results in the desired UI effect. Unfortunately, the login fails and a long error message is displayed. Perhaps someone with more knowledge of the GoogleAPI signin process can make it work. Line 142 and the full procedure are as follows:
//scope = [GTMOAuth2Authentication scopeWithStrings:scope, emailScope, nil];
- (void)addScopeForGoogleUserInfo {
GTMOAuth2Authentication *auth = self.authentication;
if (self.shouldFetchGoogleUserEmail) {
NSString *const emailScope = #"https://www.googleapis.com/auth/userinfo.email";
NSString *scope = auth.scope;
if ([scope rangeOfString:emailScope].location == NSNotFound) {
//scope = [GTMOAuth2Authentication scopeWithStrings:scope, emailScope, nil];
auth.scope = scope;
}
}
if (self.shouldFetchGoogleUserProfile) {
NSString *const profileScope = #"https://www.googleapis.com/auth/userinfo.profile";
NSString *scope = auth.scope;
if ([scope rangeOfString:profileScope].location == NSNotFound) {
scope = [GTMOAuth2Authentication scopeWithStrings:scope, profileScope, nil];
auth.scope = scope;
}
}
}
I have a UIWebView and I need to do something when user taps a link. There’s a delegate callback that can be used to detect the taps:
- (BOOL) webView: (UIWebView*) webView
shouldStartLoadWithRequest: (NSURLRequest*) request
navigationType: (UIWebViewNavigationType) navigationType
{
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
…
}
}
The problem is that this code doesn’t handle all link clicks. As an example, a plain Google Search results page does something weird with the links:
<a href="http://example.com/" class="l" onmousedown="return rwt(…)">
<em>Link Text</em>
</a>
The rwt function results in the links not triggering the UIWebViewNavigationTypeLinkClicked event when tapped. Is there a way to reliably detect all events that fall into the “navigate to some other page” bucket?
So far I have arrived at the following solution. First, I inject some JS code into the page when loaded:
function reportBackToObjectiveC(string)
{
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "callback://" + string);
document.documentElement.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;
}
var links = document.getElementsByTagName("a");
for (var i=0; i<links.length; i++) {
links[i].addEventListener("click", function() {
reportBackToObjectiveC("link-clicked");
}, true);
}
When user taps a link, I know it in advance thanks to the webView:shouldStartLoadWithRequest: navigationType: delegate call:
if ([[[request URL] scheme] isEqualToString:#"callback"]) {
[self setNavigationLeavingCurrentPage:YES];
return NO;
}
Then if another request comes and _navigationLeavingCurrentPage is true, I know the user has clicked a link even though the navigation type flag is UIWebViewNavigationTypeOther. I still have to test the solution extensively, for I’m afraid that it will lead to some false positives.
I believe this can be done, by embedding in the HTML code of the website a custom JavaScript which will monitor the events, and based on which event you want to monitor, it could trigger a page redirect with a custom URL scheme, which you can intercept in the shouldStartLoadWithRequest.
Something like this:
<script>
// Function to capture events
function captureEvent(el) {
window.location.href="callback://"+el.href;
}
var elms = document.getElementsByTagName("a");
for (var i=0; i<elms.length; i++) {
elms[i].addEventListener("onmousedown", function(){captureEvent(el)}, true);
}
</script>
Then in the shouldStartLoadWithRequest, you can search for NSURLRequest's that have a callback:// url scheme, and do whatever you want.
This has not been tested, but something like this might get you in the right direction.
Also, since this was mentioned, yes you can add your custom script to any webpage, by using this:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[super webViewDidFinishLoad:webView];
[webView stringByEvaluatingJavaScriptFromString:#"document.body.insertAdjacentHTML('BeforeEnd','<script>....</script>');"];
}
You need to add this line
webView.dataDetectorTypes = UIDataDetectorTypeAll;
then
(BOOL) webView:(UIWebView *)inWeb shouldStartLoadWithRequest:(NSURLRequest *)inRequest navigationType:(UIWebViewNavigationType)inType
method will get call.
I had a bunch of problems getting this to work. Using a UITapGestureRecognizer works, except that it will always receive the taps, even for links. Unfortunately, links have about a 300 ms delay before they get recognized, which means that the gesture recognizer gets the taps before the link is recognized, which creates a timing problem (even worse because you shouldn't hard code a time to wait in case Apple changes it). Plus, waiting 300+ ms for the tap gives a poor user experience for my app.
So what I ended up doing was overlaying a transparent div on top of everything that gets the non-link taps. Then, put the links at a higher z-level. Finally, make sure everything uses ontouchend="javascript:window.location.href='...';". ontouchend will only act on a tap release, which is what users expect. Setting window.location.href loads a new page, ensuring that all taps call -webView:shouldStartLoadWithRequest:navigationType: and I can check the URL to see which I need to do.
The HTML looks like:
...
...
...
...
(I'm not totally sure if "position: relative" always places things in the same position as otherwise, but it worked nicely for my simple page; your mileage may vary. However, you will needs position:something in order to get z-index to work.)