UIWebView stringByEvaluatingJavaScriptFromString returns empty string - objective-c

I know there have been couple of discussion already on this topic, but I am trying a different approach here.
I am trying to first inject a variable into the Webview, then try to get the output by calling a method on that variable. Here's how it goes:
webview = [[UIWebView alloc] initWithFrame:window.bounds];
[window addSubview:webview];
[webview loadHTMLString:#"<html><head><script>(function() {var myVariable = window.myVariable = {};"
#"myVariable.getVersion = function() {return 1;}})();"
#"</script></head></html>"
baseURL:nil];
webview.delegate = self;
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if ([[webView stringByEvaluatingJavaScriptFromString:#"typeof(myVariable);"] isEqualToString:#"object"]) {
NSLog(#"version=%#",[webView stringByEvaluatingJavaScriptFromString:#"myVariable.getVersion());"]);
} else {
NSLog(#"failed the test.");
}
}
The output:
version=

Related

Html form in UIwebview send back to ios?

i am trying to pass information between an HTML form and ios i tryed javascrip but it isn't working?
// Init Web View
UIWebView *webView;
webView = [[UIWebView alloc] initWithFrame:boundsRect];
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
webView.scalesPageToFit = YES;
webView.contentMode = UIViewContentModeCenter;
webView.multipleTouchEnabled = NO;
webView.userInteractionEnabled = YES;
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]]];
[self addSubview:webView];
NSString *testArrayString = [webView stringByEvaluatingJavaScriptFromString:#"testString"];
NSArray *testArray = [testArrayString componentsSeparatedByString:#"#"];
NSLog(#"%#", testArray);
[webView release];
return self;
any ideas?
----EDIT----
Ok guys if u only want to make data from a form pass to a ios app you only do this to your webview
[webView setDelegate:self];
Then you can implement this methods in your program marks
like:
-(void)webViewDidFinishLoad:(UIWebView *) webView {
//implement code to do here
}
if you want to capture a for just do a webview shouldStartWithRequest like this:
-(BOOL)webView:(UIwebView *) webView shouStartWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigation{
//implement code to do afeter the user click the form don't forget to return YES or NO because it is a BOOL
}
that is how i solve it any question please do tell me i try my best to help
To pass data from UIWebView and back to your Objective-C runtime, you need to implement UIWebViewDelegates webView:shouldStartLoadingWithRequest: and pass your form-data as parameters in the URL. If you craft all your URL's with a scheme like f.ex. my-app://?parameter1=value1&parameter2=value2, you can easily filter out the URLs the web view should not load, and return NO.
EDIT:
Added example-code.
- (void)initWebView
{
UIWebView *webView;
webView = [[UIWebView alloc] initWithFrame:boundsRect];
webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
webView.scalesPageToFit = YES;
webView.contentMode = UIViewContentModeCenter;
webView.multipleTouchEnabled = NO;
webView.userInteractionEnabled = YES;
webView.delegate = self;
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL *url = request.URL;
if ([url.scheme isEqualToString:#"http://"]) {
return YES;
}
else if ([url.scheme isEqualToString:#"my-app://"]){
// Process data from form
return NO;
}
return YES;
}
EDIT2: You are releasing the web view in your initialisation-code. This prevents the delegate-method from being called when you are loading the form-url
EDIT3: See this question for more code-examples: Invoke method in objective c code from HTML code using UIWebView

Objective C Objects Only Being Changed In Method Scope

I am making a chat application, and when the person logs onto the chat successfully, the server replies with the people that are online at that moment. That string (the server sends a string) gets converted to a NSMutableArray, which is then stored into a NSMutableArray called tableData, which is the data source for a NSTableView. When the online people are stored into tableData, the NSLog output shows that tableData is filled. However, when that method is done, and the login view is closed, in the debugger tableData says 0 Objects, and the NSTableView doesn't fill, which it normally does. Here is are my methods (one calls another):
- (void)getPeople:(NSString *)outputMessage
{
NSArray *newTableData = [outputMessage componentsSeparatedByString:#";"];
self.tableData = [NSMutableArray arrayWithArray:newTableData];
//[self.tableData removeObjectAtIndex:0];;
NSLog(#"Data: %#", self.tableData);
[self.people reloadData];
}
- (void)accessGrantedWithOnlineUsers:(NSString *)users
{
NSLog(#"Access Granted");
self.isLoggedIn = YES;
[self.loginButton setTitle:#"Logout"];
[self getPeople:users];
}
Here is my method that opens and closes the login view:
- (IBAction)loginToChat:(id)sender {
NSLog(#"Called");
if (self.loginPopover == nil) {
NSLog(#"Login Popover is nil");
self.loginPopover = [[NSPopover alloc] init];
self.loginPopover.contentViewController = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
}
if (!self.loginPopover.isShown) {
NSLog(#"Login Popover is opening");
[self.loginButton setTitle:#"Cancel"];
[self.settingsButton setEnabled:NO];
[self.send setEnabled:NO];
[self.message setEnabled:NO];
[self.loginPopover showRelativeToRect:self.loginButton.frame ofView:self.view preferredEdge:NSMinYEdge];
}
else {
NSLog(#"Login Popover is closing");
if (self.isLoggedIn) {
[self.loginButton setTitle:#"Logout"];
}
else {
[self.loginButton setTitle:#"Login"];
}
[self.settingsButton setEnabled:YES];
[self.send setEnabled:YES];
[self.message setEnabled:YES];
[self.loginPopover close];
}
}
Any help would be greatly appreciated because I have a deadline for this project.
Instead of
self.tableData = [NSMutableArray arrayWithArray:newTableData];
can you try
self.tableData = [newTableData copy];
The only thing i can think of is - is the tableData a strong property?
After this
self.tableData = [NSMutableArray arrayWithArray:newTableData];
write this snippet:
[self setTableData:tableData]

Objective-C: Why is my if statement not working?

Here i have some code that checks if the iphone has a camera flash. And then check if my switch is on by using this code:
if(switch1.on){
}
The light turns on fine without this code and i have also tried this code as well:
if(!(switch1.on)){
}
and that successfully turns my camera flash on but also turns it on even when the switch is set to off
Here is my full code:
-(IBAction)torchon:(id)sender{
AVCaptureDevice *flashlight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([flashlight isTorchAvailable] & [flashlight isTorchModeSupported:AVCaptureTorchModeOn]) {
BOOL success = [flashlight lockForConfiguration:Nil];
if(success){
if(switch1.on){ //// or (!(switch1.on))
on.hidden = YES;
[UIScreen mainScreen].brightness = 1.0;
[flashlight setTorchMode:AVCaptureTorchModeOn];
[flashlight unlockForConfiguration];
}
}
}
}
any help would be appreciated
**EDIT:**
Here is the code where i set the switch to no using ibactions when the value of the switch has changed. This code works successfully:
-(IBAction)switch1{
if (switch1.on) {
switch1.on = YES;
switch2.on = NO;
} else{
switch2.on = YES;
switch1.on = NO;
}
}
-(IBAction)switch2{
if (switch2.on) {
switch2.on = YES;
switch1.on = NO;
} else{
switch1.on = YES;
switch2.on = NO;
}
}
Answer
First add a bool:
bool yes;
then set that bool in the viewdidload
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
yes = YES;
}
After that set it to YES when the ibaction is called and set it to NO once the switch is turned off:
-(IBAction)switch1{
if (swich1.on) {
swich1.on = YES;
swich2.on = NO;
yes = YES;
}
}
-(IBAction)switch2{
if (swich2.on) {
swich2.on = YES;
swich1.on = NO;
yes = NO;
}
}
lastly where it checks if(switch1.on){} you need to change it to if(yes==YES){} so together that if statement looks like this:
-(IBAction)torchon:(id)sender{
AVCaptureDevice *flashlight = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([flashlight isTorchAvailable] & [flashlight isTorchModeSupported:AVCaptureTorchModeOn]) {
BOOL success = [flashlight lockForConfiguration:Nil];
if(success){
if(yes==YES){
on.hidden = YES;
[UIScreen mainScreen].brightness = 1.0;
[flashlight setTorchMode:AVCaptureTorchModeOn];
[flashlight unlockForConfiguration];
}
}
}
}
The UISwitch has another getter for the on property.
#property(nonatomic, getter=isOn) BOOL on;
It may be better to use .isOn instead of .on for getting the value.
Also, some of your code can be simplified:
-(IBAction)switch1{
[switch2 setOn:!switch1.isOn];
}
-(IBAction)switch2{
[switch1 setOn:!switch2.isOn];
}
It can get even better if you use the sender of the action in your methods.
It seems that your IBAction methods have a similar form as the getter for the switch. It is possible that this could lead to undesired side effects. Refactor your methods to a single method, more in line with good programming practice:
-(IBAction)switchDidChange:(UISwitch*)aSwitch {
UISwitch* otherSwitch = (aSwitch == switch1)? switch2 : switch1;
[otherSwitch setOn:!aSwitch.on animated:YES];
}
Also, in your code the on property does not really seem to influence what is switched on or off. Include this conditionality.

How do I animate fadeIn fadeOut effect in NSTextField when changing its text?

I am trying to write a category for NSTextField which will add a new method setAnimatedStringValue. This method is supposed to nicely fade-out the current text, then set the new text and then fade that in.
Below is my implementation:-
- (void) setAnimatedStringValue:(NSString *)aString {
if ([[self stringValue] isEqualToString:aString]) {
return;
}
NSMutableDictionary *dict = Nil;
NSViewAnimation *fadeOutAnim;
dict = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
NSViewAnimationFadeOutEffect, NSViewAnimationEffectKey, nil];
fadeOutAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:
dict, nil]];
[fadeOutAnim setDuration:2];
[fadeOutAnim setAnimationCurve:NSAnimationEaseOut];
[fadeOutAnim setAnimationBlockingMode:NSAnimationBlocking];
NSViewAnimation *fadeInAnim;
dict = [NSDictionary dictionaryWithObjectsAndKeys:self, NSViewAnimationTargetKey,
NSViewAnimationFadeInEffect, NSViewAnimationEffectKey, nil];
fadeInAnim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:
dict, nil]];
[fadeInAnim setDuration:3];
[fadeInAnim setAnimationCurve:NSAnimationEaseIn];
[fadeInAnim setAnimationBlockingMode:NSAnimationBlocking];
[fadeOutAnim startAnimation];
[self setStringValue:aString];
[fadeInAnim startAnimation];
}
Needless to say, but the above code does not work at all. The only effect I see is the flickering of a progress bar on the same window. That is possibly because I am blocking the main runloop while trying to "animate" it.
Please suggest what is wrong with the above code.
Additional note:
setAnimatedStringValue is always invoked by a NSTimer, which is added to the main NSRunLoop.
I was poking around a bit after posting the previous answer. I'm leaving that answer because it corresponds closely to the code you posted, and uses NSViewAnimation. I did, however, come up with a considerably more concise, albeit slightly harder to read (owing to block parameter indentation) version that uses NSAnimationContext instead. Here 'tis:
#import <QuartzCore/QuartzCore.h>
#interface NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString;
#end
#implementation NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString
{
if ([[self stringValue] isEqual: aString])
{
return;
}
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setDuration: 1.0];
[context setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut]];
[self.animator setAlphaValue: 0.0];
}
completionHandler:^{
[self setStringValue: aString];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext *context) {
[context setDuration: 1.0];
[context setTimingFunction: [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn]];
[self.animator setAlphaValue: 1.0];
} completionHandler: ^{}];
}];
}
#end
Note: To get access to the CAMediaTimingFunction class used here for specifying non-default timing functions using this API, you'll need to include QuartzCore.framework in your project.
Also on GitHub.
For Swift 3, here are two convenient setText() and setAttributedText() extension methods that fade over from one text to another:
import Cocoa
extension NSTextField {
func setStringValue(_ newValue: String, animated: Bool = true, interval: TimeInterval = 0.7) {
guard stringValue != newValue else { return }
if animated {
animate(change: { self.stringValue = newValue }, interval: interval)
} else {
stringValue = newValue
}
}
func setAttributedStringValue(_ newValue: NSAttributedString, animated: Bool = true, interval: TimeInterval = 0.7) {
guard attributedStringValue != newValue else { return }
if animated {
animate(change: { self.attributedStringValue = newValue }, interval: interval)
}
else {
attributedStringValue = newValue
}
}
private func animate(change: #escaping () -> Void, interval: TimeInterval) {
NSAnimationContext.runAnimationGroup({ context in
context.duration = interval / 2.0
context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
animator().alphaValue = 0.0
}, completionHandler: {
change()
NSAnimationContext.runAnimationGroup({ context in
context.duration = interval / 2.0
context.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
self.animator().alphaValue = 1.0
}, completionHandler: {})
})
}
}
Call them as following:
var stringTextField: NSTextField
var attributedStringTextField: NSTextField
...
stringTextField.setStringValue("New Text", animated: true)
...
let attributedString = NSMutableAttributedString(string: "New Attributed Text")
attributedString.addAttribute(...)
attributedStringTextField.setAttributedStringValue(attributedString, animated: true)
I'll take a stab:
I found a couple problems here. First off, this whole thing is set up to be blocking, so it's going to block the main thread for 5 seconds. This will translate to the user as a SPOD/hang. You probably want this to be non-blocking, but it'll require a little bit of extra machinery to make that happen.
Also, you're using NSAnimationEaseOut for the fade out effect, which is effected by a known bug where it causes the animation to run backwards. (Google for "NSAnimationEaseOut backwards" and you can see that many have hit this problem.) I used NSAnimationEaseIn for both curves for this example.
I got this working for a trivial example with non-blocking animations. I'm not going to say that this is the ideal approach (I posted a second answer that arguably better), but it works, and can hopefully serve as a jumping off point for you. Here's the crux of it:
#interface NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString;
#end
#interface SOTextFieldAnimationDelegate : NSObject <NSAnimationDelegate>
- (id)initForSettingString: (NSString*)newString onTextField: (NSTextField*)tf;
#end
#implementation NSTextField (AnimatedSetString)
- (void) setAnimatedStringValue:(NSString *)aString
{
if ([[self stringValue] isEqual: aString])
{
return;
}
[[[SOTextFieldAnimationDelegate alloc] initForSettingString: aString onTextField: self] autorelease];
}
#end
#implementation SOTextFieldAnimationDelegate
{
NSString* _newString;
NSAnimation* _fadeIn;
NSAnimation* _fadeOut;
NSTextField* _tf;
}
- (id)initForSettingString: (NSString*)newString onTextField: (NSTextField*)tf
{
if (self = [super init])
{
_newString = [newString copy];
_tf = [tf retain];
[self retain]; // we'll autorelease ourselves when the animations are done.
_fadeOut = [[NSViewAnimation alloc] initWithViewAnimations: #[ (#{
NSViewAnimationTargetKey : tf ,
NSViewAnimationEffectKey : NSViewAnimationFadeOutEffect})] ];
[_fadeOut setDuration:2];
[_fadeOut setAnimationCurve: NSAnimationEaseIn];
[_fadeOut setAnimationBlockingMode:NSAnimationNonblocking];
_fadeOut.delegate = self;
_fadeIn = [[NSViewAnimation alloc] initWithViewAnimations: #[ (#{
NSViewAnimationTargetKey : tf ,
NSViewAnimationEffectKey : NSViewAnimationFadeInEffect})] ];
[_fadeIn setDuration:3];
[_fadeIn setAnimationCurve:NSAnimationEaseIn];
[_fadeIn setAnimationBlockingMode:NSAnimationNonblocking];
[_fadeOut startAnimation];
}
return self;
}
- (void)dealloc
{
[_newString release];
[_tf release];
[_fadeOut release];
[_fadeIn release];
[super dealloc];
}
- (void)animationDidEnd:(NSAnimation*)animation
{
if (_fadeOut == animation)
{
_fadeOut.delegate = nil;
[_fadeOut release];
_fadeOut = nil;
_tf.hidden = YES;
[_tf setStringValue: _newString];
_fadeIn.delegate = self;
[_fadeIn startAnimation];
}
else
{
_fadeIn.delegate = nil;
[_fadeIn release];
_fadeIn = nil;
[self autorelease];
}
}
#end
It would be really nice if there were block-based API for this... it'd save having to implement this delegate object.
I put the whole project up on GitHub.

TTModel load method not being called

Issue
Description
The following code results in load method of TTModel not being called. I've stepped it through the debugger, as well as stepping through the TTCatalog application. The only difference between the two that I can see, is that when the catalog sets it's DataSource in the createModel method of the controller, TTModel's load method gets called, whereas mine does not.
About the Code
I've commented the specific areas of code, to tell what they should be doing and what problem is happening, but I included all of it for completion's sake.
You should look at specifically
PositionsController's createModel method
PositionsList's load method
Those are the areas where the issue is, and would be the best place to start.
Code
PositionsController : TTTableViewController
- (id)initWIthNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.title = #"Positions";
self.variableHeightRows = NO;
self.navigationBarTintColor = [UIColor colorWithHexString:#"1F455E"];
}
return self;
}
//This method here should result in a call to the PositionsList load method
- (void)createModel
{
PositionsDataSource *ds = [[PositionsDataSource alloc] init];
self.dataSource = ds;
[ds release];
}
- (void)loadView
{
[super loadView];
self.view = [[[UIView alloc] initWithFrame:TTApplicationFrame()] autorelease];
self.tableView = [[[UITableView alloc] initWithFrame:TTApplicationFrame() style:UITableViewStylePlain] autorelease];
self.tableView.backgroundColor = [UIColor colorWithHexString:#"E2E7ED"];
self.tableView.separatorColor = [UIColor whiteColor];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
//Override UITableViewDelegate creation method, so we can add drag to refresh
- (id<TTTableViewDelegate>) createDelegate {
TTTableViewDragRefreshDelegate *delegate = [[TTTableViewDragRefreshDelegate alloc] initWithController:self];
return [delegate autorelease];
}
PositionsDataSource : TTListDataSource
#implementation PositionsDataSource
#synthesize positionsList = _positionsList;
-(id)init
{
if (self = [super init])
{
_positionsList = [[PositionsList alloc] init];
self.model = _positionsList;
}
return self;
}
-(void)dealloc
{
TT_RELEASE_SAFELY(_positionsList);
[super dealloc];
}
-(void)tableViewDidLoadModel:(UITableView*)tableView
{
self.items = [NSMutableArray array];
}
-(id<TTModel>)model
{
return _positionsList;
}
#end
PositionsList : NSObject <TTModel>
#implementation PositionsList
//============================================================
//NSObject
- (id)init
{
if (self = [super init])
{
_delegates = nil;
loaded = NO;
client = [[Client alloc] init];
}
return self;
}
- (void)dealloc
{
TT_RELEASE_SAFELY(_delegates);
[client dealloc];
[super dealloc];
}
//==============================================================
//TTModel
- (NSMutableArray*)delegates
{
if (!_delegates)
{
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
-(BOOL)isLoadingMore
{
return NO;
}
-(BOOL)isOutdated
{
return NO;
}
-(BOOL)isLoaded
{
return loaded;
}
-(BOOL)isEmpty
{
//return !_positions.count;
return NO;
}
-(BOOL)isLoading
{
return YES;
}
-(void)cancel
{
}
//This method is never called, why is that?
-(void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more
{
//This method is not getting called
//When the PositionsController calls self.datasource, load should be called,
//however it isn't.
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
[client writeToServer:self dataToSend:#""];
}
-(void)invalidate:(BOOL)erase
{
}
#end
Short answer: return NO instead of YES for isLoading in your PositionList.
For a longer explanation:
If you dig through the Three20 source, you'll find that setting the dataSource on a view controller sets the model, refreshing the model and potentially calling load. Here is the code called when the TTModelViewController refreshes:
- (void)refresh {
_flags.isViewInvalid = YES;
_flags.isModelDidRefreshInvalid = YES;
BOOL loading = self.model.isLoading;
BOOL loaded = self.model.isLoaded;
if (!loading && !loaded && [self shouldLoad]) {
[self.model load:TTURLRequestCachePolicyDefault more:NO];
} else if (!loading && loaded && [self shouldReload]) {
[self.model load:TTURLRequestCachePolicyNetwork more:NO];
} else if (!loading && [self shouldLoadMore]) {
[self.model load:TTURLRequestCachePolicyDefault more:YES];
} else {
_flags.isModelDidLoadInvalid = YES;
if (_isViewAppearing) {
[self updateView];
}
}
}
Your PositionList object is returning YES for isLoading and NO for isLoaded. This means that Three20 thinks your model is loading when it isn't. You may also need to implement shouldLoad if it doesn't return YES by default.