I'm trying to use delegate methods from NMSSH library in iOS but could not get it working. Let's take an example.
CustomViewController.h
#import <UIKit/UIKit.h>
#import <NMSSH/NMSSH.h>
#interface CustomViewController : UIViewController<NMSSHSessionDelegate, NMSSHChannelDelegate>
- (IBAction)connectButton:(UIButton *)sender;
#end
CustomViewController.m
#import "CustomViewController.h"
#implementation CustomViewController
-(void)viewDidLoad{
[super viewDidLoad];
}
- (IBAction)connectButton:(UIButton *)sender {
[self serverConnect:#"10.0.0.1"];
}
-(void)serverConnect:(NSString *)address{
NMSSHSession *session = [NMSSHSession connectToHost:address withUsername:#"username"];
NMSSHChannel *myChannel = [[NMSSHChannel alloc]init];
if (session.isConnected) {
[session authenticateByPassword:#"password"];
if (session.isAuthorized) {
NSLog(#"Authentication succeeded");
[session setDelegate:self];
[myChannel setDelegate:self];
}
}
NSError *error = nil;
//session.channel.requestPty = YES; (tried and later ignored)
NSString *response = [session.channel execute:#"mkdir" error:&error];
NSLog(#"Response from device: %#", response);
}
- (void)session:(NMSSHSession *)session didDisconnectWithError:(NSError *)error{
NSLog(#"log if session disconnects...Delegate method");
}
- (void)channel:(NMSSHChannel *)channel didReadError:(NSString *)error{
NSLog(#"Error received...Delegate method");
}
- (void)channel:(NMSSHChannel *)channel didReadRawData:(NSData *)data{
NSLog(#"Read Raw Data...Delegate method");
}
Connection to the server, sending a single line command and acknowledgement back from the server in Console is OK.
I have decent idea how to pass values from one View Controller to another using delegate (went through few tutorials with practical implementation).
With the same knowledge I am attempting to get response from delegate methods parts of NMSSH library but it's driving me round and round. I've found http://cocoadocs.org/docsets/NMSSH/2.2.1/ pretty nice API of this library but with my limited knowledge of iOS, I'm bit stuck.
Please help me.
My search finally came to an end with NMSSH AsyncAPI (branch) which supports multithreading.
View of iOS 8 action extension app has UIWebView. When I open this action extension in Safari and then extension app shows UIWebView of mobile Safari's URL.
But extension app crashes sometimes during loading web page or scrolling it for some web pages like nytimes.com.
I know extension app's usable memory depends on mobile Safari.
But I found that 'Awesome Screenshot for Safari' does not crash.
(https://itunes.apple.com/us/app/awesome-screenshot-for-safari/id918780145)
I am wondering how to prevent action extension app from crashing.
#interface ActionViewController ()
#property(strong,nonatomic) IBOutlet UIImageView *imageView;
#property (strong, nonatomic) IBOutlet UIWebView *webView;
#end
#implementation ActionViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSExtensionItem *item = self.extensionContext.inputItems.firstObject;
NSItemProvider *itemProvider = item.attachments.firstObject;
if ([itemProvider hasItemConformingToTypeIdentifier:(NSString *)kUTTypeURL]) {
__weak typeof(self) weakSelf = self;
[itemProvider loadItemForTypeIdentifier:(NSString *)kUTTypeURL options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
if (error) {
[weakSelf.extensionContext cancelRequestWithError:error];
return;
}
if (![(NSObject *)item isKindOfClass:[NSURL class]]) {
NSError *unexpectedError = [NSError errorWithDomain:NSItemProviderErrorDomain
code:NSItemProviderUnexpectedValueClassError
userInfo:nil];
[weakSelf.extensionContext cancelRequestWithError:unexpectedError];
return;
}
NSURL *url = (NSURL *)item;
[weakSelf.webView loadRequest:[NSURLRequest requestWithURL:url]];
}];
} else {
NSError *unavailableError = [NSError errorWithDomain:NSItemProviderErrorDomain
code:NSItemProviderItemUnavailableError
userInfo:nil];
[self.extensionContext cancelRequestWithError:unavailableError];
}
}
I was experiencing the same issue, using a UIWebView in the background to scrape some content for my app. It worked fine when I was connected to the debugger. But running the same build without the debugger always crashed.
I resolved it by migrating to WKWebView, which was actually pretty easy. I guess UIWebView is just too old and inefficient to be running in an extension inside of Safari. Using WKWebView worked perfectly.
I then discovered a crash when I was done using the web view and was saving my data. I was saving it to NSUserDefaults (using my group container) to pass to the main app. This was always crashing too, so I removed that code. I ended up using CoreData instead, which worked fine too.
Now I have a crash free Safari extension! :)
I have a UITableView with 2 text field and a button. If I run the simulator without use custom class, I can see the text fields and button:
But when i use a custom class, my UITable view only display a lot of lines without content:
Here is how I've created my properties:
LoginSceneController.h
#import <UIKit/UIKit.h>
#interface LoginSceneController : UITableViewController
#property (nonatomic, strong) IBOutlet UITextField *email;
#property (nonatomic, strong) IBOutlet UITextField *password;
- (IBAction)doLogin;
#end
LoginSceneController.m
#import "LoginSceneController.h"
#interface LoginSceneController ()
#end
#implementation LoginSceneController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (void)doLogin {
NSURL *url = [NSURL URLWithString:#"http://rest-service.guides.spring.io/greeting"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response,
NSData *data, NSError *connectionError)
{
if (data.length > 0 && connectionError == nil)
{
NSDictionary *greeting = [NSJSONSerialization JSONObjectWithData:data
options:0
error:NULL];
self.email.text = [[greeting objectForKey:#"id"] stringValue];
self.password.text = [greeting objectForKey:#"content"];
}
}];
}
#end
The problem happens when I use a custom class (or referencing outlet or add a send event on button).
What is wrong?
edit: I think that I need populate my interface using my custom class because the static content is being lost. Is it possible to be the cause of content being lost?
You have two options when it comes to UITableView and Interface Builder. You can have a dynamic table view (pretty common) where your code overrides UITableViewController methods like "numberOfRowsInSection" and "cellForRowAtIndexPath". The other option is a static tableview, and that seems like what you want to do (especially since you haven't overridden the two aforementioned methods, and leads to your blank table). My guess is you need to select "static" for the tableview as shown in the third screenshot in this tutorial.
I want to display a website on an iOS app using a UiWebView. Some components of the site (namely the webservice results loaded using AJAX calls) should be replaced by local data.
Consider the following example:
text.txt:
foo
page1.html:
<html><head>
<title>test</title>
<script type="text/javascript" src="jquery.js"></script>
</head>
<body>
<div id="target"></div>
<script type="text/javascript">
function init(){
$.get("text.txt",function(text){
$("#target").text(text);
});
}
$(init);
</script>
</body></html>
View Controller:
#interface ViewController : UIViewController <UIWebViewDelegate>
#property (nonatomic,assign) IBOutlet UIWebView *webview;
#end
#implementation ViewController
#synthesize webview;
//some stuff here
- (void)viewDidLoad
{
[super viewDidLoad];
[NSURLProtocol registerClass:[MyProtocol class]];
NSString *url = #"http://remote-url/page1.html";
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
[webview loadRequest:request];
}
#end
MyProtocol:
#interface MyProtocol : NSURLProtocol
#end
#implementation MyProtocol
+ (BOOL) canInitWithRequest:(NSURLRequest *)req{
NSLog(#"%#",[[req URL] lastPathComponent]);
return [[[req URL] lastPathComponent] caseInsensitiveCompare:#"text.txt"] == NSOrderedSame;
}
+ (NSURLRequest*) canonicalRequestForRequest:(NSURLRequest *)req{
return req;
}
- (void) startLoading{
NSLog(#"Request for: %#",self.request.URL);
NSString *response_ns = #"bar";
NSData *data = [response_ns dataUsingEncoding:NSASCIIStringEncoding];
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[self.request URL] MIMEType:#"text/plain" expectedContentLength:[data length] textEncodingName:nil];
[[self client] URLProtocol: self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
[[self client] URLProtocol:self didLoadData:data];
[[self client] URLProtocolDidFinishLoading:self];
[response release];
}
- (void) stopLoading{
NSLog(#"stopLoading");
}
#end
If I don't register my custom URLProtocol the page is displayed properly. If I do startLoading() is called, the content is loaded and stopLoading() is triggered afterwards. But on the UIWebView nothing happends at all. I tried to do some error-handling, but neither is a JS AJAX error thrown nor is didFailLoadWithError of the UIWebViewDelegate called.
I tried another scenario and created a HTML page that just loads an image:
<img src="image.png" />
and modified my URLProtocol to just handle the loading of the image - this works properly. Maybe this has anything to do with AJAX calls?
Do you have any idea what the problem might be?
Thanks in advance!
I had the same problem and finally solved it after days of hair pulling :
Your problem comes from the way you create the response, you have to create a status 200 response and force the WebView to allow cross-domain request if necessary :
NSDictionary *headers = #{#"Access-Control-Allow-Origin" : #"*", #"Access-Control-Allow-Headers" : #"Content-Type"};
NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:200 HTTPVersion:#"1.1" headerFields:headers];
You can see my full working implementation in my answer here :
How to mock AJAX call with NSURLProtocol?
Hope this helps,
Vincent
Similar question here: jQueryMobile, Phonegap and Device Token - iOS
The scenario is, I have this PhoneGap web based application, and the native iOS help me registered the device on APN and I received the device token in my server database.
Question 1: How do you associate a registered user (through the UIWebView) to this device token using PhoneGap?
What's in my mind now is to write a custom plugin and pass the device token along during user registration. Is there any better alternative?
Question 2: Since device_token can be changed from time to time, how should I re-link this user to this device_token?
Perhaps whenever the user login, I do a window.plugins.PluginName.getDeviceToken and sync it?
{user_id:123, old_device_token: 'xxxx..', new_device_token: 'xxx...'}?
Fyi, this application is built for an event and the client has requested people to people messaging on this mobile app. How do you push a new message notification to "John Doe" when he received a message from his friend? - Question is how to link "John Doe" to a specific device_token?
This could not be too iOS specific as this application has to be deployed on Android as well (C2DM).
Any help is welcomed!
Edit: Possible solution?
Restless research emerges this possible solution:
[Native] Application started - APN registration started and device_token is received
[Native] Store this device_token into local store (CoreData/SqlLite or Property Lists?) and send it to server to do device_token registration
[WebView] Whenever a user is login or signup, this device_token will be queried through PhoneGap, hashed and sent over to the server to do a sign in, comparison and link up.
Any unforeseen scenario is problematic?
EDIT: Answer
I have my complete working solution posted in the answer. Check it out here: https://stackoverflow.com/a/9628592/534862
For the sake of completeness, this is my solution after using #TDeBailleul solution. Ported over to Cordova. (Tested on iOS only. After I have finished Android version I will post a plugin for this:
Environment
Cordova 1.5.0 (Formerly PhoneGap)
Xcode 4.3.1
iOS 5.1
Mac OS X 10.7.3
Do not use Automatic Reference Counting (ARC) to avoid compilation error
Go through this freaking long tutorial to have your Push Notification certificate ready
Working Flow
[Native] Application started - APN registration started and device_token is received at server side
[Native] Application store the token in AppDelegate, and to get the token, use the PushToken in the following codes
[WebView] Whenever a user is login or signup, the token will be called through Cordova (formerly PhoneGap) plugin, hashed and being sent over to the server to do a sign in, matching and link up the user to a particular device.
So below is my complete working codes that do the native pulling of token. Server part is nothing but just linking accounts to devices. You should know how to do it through your favourite server side application.
AppDelegate.h
#interface AppDelegate : NSObject < UIApplicationDelegate, UIWebViewDelegate, CDVCommandDelegate > {
...
NSString* token;
...
}
...
...
...
#property (retain, nonatomic) NSString* token;
AppDelegate.m
Note: I'm a Obj-C newbie and I have included my attempt to post token to my server as well. If you do not need the posting mechanism, retain the first 5 lines (til' self.token = dt) in the didRegisterForRemoteNotificationsWithDeviceToken
...
#synthesize token;
...
- (BOOL) application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
...
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
NSLog(#"Did finish launching with device token %#", deviceToken);
NSString *dt = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]];
dt = [dt stringByReplacingOccurrencesOfString:#" " withString:#""];
self.token = dt;
NSString *dv = [[UIDevice currentDevice] systemVersion];
NSString *dn = [[UIDevice currentDevice] systemName];
NSString *nick = [[UIDevice currentDevice] name];
NSString *model = [[UIDevice currentDevice] model];
NSString *uniqueIdentifier = [[UIDevice currentDevice] uniqueIdentifier];
NSMutableString *postData = [NSMutableString stringWithString: #"&deviceToken="];
[postData appendString:dt];
[postData appendFormat:#"&uniqueIdentifier=%#&application_uuid=5ade8400-e29c-41d4-a716-3641972a2ec6", uniqueIdentifier];
[postData appendFormat:#"&source=ios&name=%#&model=%#&systemName=%#&systemVersion=%#", nick, model, dn, dv];
NSString* escapedURLString = [postData stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
#try {
NSData *postData = [escapedURLString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL: [NSURL URLWithString:#"{YOUR URL TO POST TOKEN TO SERVER}"]
cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval: 180];
NSString *postLength = [[NSString alloc] initWithFormat: #"%d", [postData length]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self];
if (conn) {
//??
}else{
//??
}
}
#catch (NSException *exception) {
NSLog(#"Exception %#", exception);
}
}
...
PushToken.h
#import <Foundation/Foundation.h>
#import <CORDOVA/CDVPlugin.h>
#interface PushToken : CDVPlugin {
NSString* callbackID;
}
#property (nonatomic, copy) NSString* callbackID;
- (void) getToken:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
#end
PushToken.m
#import "PushToken.h"
#import "AppDelegate.h"
#implementation PushToken
#synthesize callbackID;
- (void)getToken:(NSMutableArray *)arguments withDict:(NSMutableDictionary *)options {
NSLog(#"Did called getToken");
self.callbackID = [arguments pop];
NSString *token = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).token;
CDVPluginResult *pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:[token stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if (token.length != 0) {
[self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]];
}else{
[self writeJavascript: [pluginResult toErrorCallbackString:self.callbackID]];
}
}
#end
PushToken.js
var PushToken = {
/**
* Get token from the device
* #param {array} types - By default is ['getToken']
* #param {function} success Success callback, with token
* #param {function} fail Failure callback, with null
*/
getToken: function(types, success, fail) {
return Cordova.exec(success, fail, "PushToken", "getToken", types);
},
/**
* For the sake of iOS, we need an install function
*/
install: function() {
window.plugins = window.plugins || {};
window.plugins.PushToken = PushToken;
}
};
/**
* For the rest of the devices
*/
window.plugins = window.plugins || {};
window.plugins.PushToken = PushToken;
Usage
document.addEventListener('deviceready', function() {
if (typeof PushToken == 'object') {
PushToken.install();
}
PushToken.getToken(['getToken'], function(token) {
callback(token);
}, function() {
callback(null);
});
});
And you should be able to get your token from your Javascript side ;)
Have fun!
Cheers
The previous answers are really great ways to solve this problem and they do it in a very formal fashion. However, if you want a quick and dirty method of solving the problem, you can simply do this:
at the bottom of didRegisterForRemoteNotificationsWithDeviceToken add
NSString* jsString = [NSString stringWithFormat:#"var deviceToken = \"%#\";", deviceToken];
[self.viewController.webView stringByEvaluatingJavaScriptFromString:jsString];
In your javascript
deviceId = (typeof deviceToken !== "undefined") ? deviceToken : null;
Ok, I finally made a plugin that seems to work
1 - Make sure your PhoneGap Xcode project has been updated for the iOS 4 SDK.
2 - Create a PushToken folder in your Plugins folder add the following PushToken.m and PushToken.h files to it and then drag the folder to the Plugin folder in XCode, using "Create groups for any added folders"
3 - Add the PushToken.js files to your www folder on disk, and add reference(s) to the .js files as tags in your html file(s)
4 - Add new entry with key PushToken and string value PushToken to Plugins in PhoneGap.plist
PushToken.h
#import <Foundation/Foundation.h>
#import <PhoneGap/PGPlugin.h>
#interface PushToken : PGPlugin{
NSString* callbackID;
}
#property (nonatomic, copy) NSString* callbackID;
- (void) getToken:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;
#end
PushToken.m
#import "PushToken.h"
#import "AppDelegate.h"
#implementation PushToken
#synthesize callbackID;
-(void)getToken:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
self.callbackID = [arguments pop];
NSString *token = ((AppDelegate *)[[UIApplication sharedApplication] delegate]).token;
PluginResult* pluginResult = [PluginResult resultWithStatus:PGCommandStatus_OK messageAsString:[token stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
if(token.length != 0)
{
[self writeJavascript: [pluginResult toSuccessCallbackString:self.callbackID]];
}else {
[self writeJavascript: [pluginResult toErrorCallbackString:self.callbackID]];
}
}
#end
PushToken.js
var PushToken = {
getToken: function(types, success, fail) {
return PhoneGap.exec(success, fail, "PushToken", "getToken", types);
}
};
How to use
PushToken.getToken(
["getToken"] ,
function(token) {
console.log("Token : "+token);
},
function(error) {
console.log("Error : \r\n"+error);
}
);
AppDelegate.h
#interface AppDelegate : PhoneGapDelegate {
NSString* invokeString;
NSString* token;
}
#property (copy) NSString* invokeString;
#property (retain, nonatomic) NSString* token;
AppDelegate.m
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
self.token = [[[[deviceToken description]
stringByReplacingOccurrencesOfString: #"<" withString: #""]
stringByReplacingOccurrencesOfString: #">" withString: #""]
stringByReplacingOccurrencesOfString: #" " withString: #""];
NSLog(#"My token is: %#", self.token);
}
I made it work on PhoneGap 1.1
Hope this helps