I'm trying to breath life back into a project from iOS 7.1 onto iOS 8.1. Obviously this is a NewsStand app. I need to be able to validate a certificate manually against a different host name. This all happens inside of willSendRequestForAuthenticationChallenge. On iOS 7.1 this all works fine. I even retested with an iPad still running 7.1 and it works, but on iOS 8.1 the method is never being invoked. As a result all Asset downloads fail with didFailWithError being invoked and an error of "The operation couldn’t be completed. (NSURLErrorDomain error -1202.)"
Does anyone know What might have changed in iOS 8.1 that would cause this? Are they not making the call? Did anything change with regards to inheritance on iOS 8.1 that might effect whether methods are detected and called? I did try moving all methods into the top class, but that didn't seem to help. Is it possible this is a result of using a "POST" request?
For the life of me I don't see any obvious code reason why willSendRequestForAuthenticationChallenge is not getting called on iOS 8.1 for NKAssetDownload, but is called for iOS 7.1. I never tested with iOS 8.0, so I can't say for sure it wasn't called there.
Note that the method is called for all my other URL connections I am using on iOS 8.1, so the issue seems to be specific to NKAssetDownload (or at least my invocations of NKAssetDownload).
The relevant code is:
#implementation myDownloader
{
NKIssue * theIssue; // pointer to the magazine Issue object
NSString * theJsonReceiptArrayBase64String;
NKAssetDownload * theAssetDownload;
NSString * sourceURL;
NSString * theFileName;
NSString * issueUniqueId;
NSURLConnection * theConnection;
NSNumber * expectedFileSize;
}
...
-(myDownloader *)initWithIssueAndIdAndSourceAndFinalPath:(NKIssue *)issue uniqueId:(NSString *)uniqueId source:(NSString *)source fileName:(NSString *)fileName fileSize:(NSNumber *) fileSize Receipt:(NSString *)receipt
{
NSLog(#"initWithIssueAndIdAndSourceAndFinalPath");
self = [super init];
if (self)
{
theIssue = issue;
sourceURL = source;
theFileName = fileName;
issueUniqueId = uniqueId;
expectedFileSize = fileSize;
theJsonReceiptArrayBase64String = receipt;
}
return self;
}
...
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
/* never get here on iOS 8.1 */
return NO;
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
/* never get here on iOS 8.1 */
[self handleAuthenticationOnConnection:connection withChallenge:challenge];
}
...
-(void) downloadPackage
{
CCommunictionMgr * communicator = [CCommunictionMgr instance];
NSMutableURLRequest * thePackageRequest = [communicator generateJsonPostPackageRequest:sourceURL uniqueId:issueUniqueId recieptData:theJsonReceiptArrayBase64String];
theAssetDownload = [theIssue addAssetWithRequest:thePackageRequest];
[theAssetDownload setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:kASSET_TYPE_PACKAGE, kASSET_TYPE_DK, nil]];
theConnection = [theAssetDownload downloadWithDelegate:self];
}
the object "myDownloader" implements both NSURLConnectionDelegate, NSURLConnectionDownloadDelegate
headerfile
#interface myDownloader : CConnectionDelegate <NSURLConnectionDelegate, NSURLConnectionDownloadDelegate>
...
where the guts of the authentication challenge are handled inside the CConnectionDelegate object.
I am happy to include the guts of other code if needed.
Related
I'm setting up a simple WKNavigationDelegate in my dynamic framework to get WKWebView's default user agent string:
#interface MyDelegate: NSObject <WKNavigationDelegate>
#end
static NSString *_defaultUserAgent;
static WKWebView *_defaultWebView;
static MyDelegate *_myDelegate;
#implementation MyDelegate
- (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation {
DispatchHelper.runOnMain = ^{
[_defaultWebView evaluateJavaScript:#"navigator.userAgent" completionHandler:^(id __nullable userAgent, NSError * __nullable error) {
_defaultUserAgent = userAgent;
_defaultWebView = nil;
_myDelegate = nil;
}];
};
}
#end
#implementation WKWebView (Util)
+ (void)load {
_myDelegate = MyDelegate.new;
WKWebView *wkWebView = WKWebView.new;
wkWebView.navigationDelegate = _myDelegate;
[wkWebView loadHTMLString:#"<HTML><BODY>TEST</BODY></HTML>" baseURL:nil];
_defaultWebView = wkWebView;
}
#end
Is this safe, or is +load too early to try something like this? In my testing I haven't noticed any issues with it, but after reading this Mike Ash blog, he says using +load is dangerous/tricky.
Specifically from the blog:
Keep in mind that there's no autorelease pool present at loading time
(usually) so you'll need to wrap your code in one if you're calling
into Objective-C stuff.
Am I at risk here by not using #autoreleasepool? I'm confused on how adding
+ (void)load {
#autoreleasepool {
_myDelegate = MyDelegate.new;
WKWebView *wkWebView = WKWebView.new;
wkWebView.navigationDelegate = _myDelegate;
[wkWebView loadHTMLString:#"<HTML><BODY>TEST</BODY></HTML>" baseURL:nil];
_defaultWebView = wkWebView;
}
}
helps me here.
+load is rife with danger and fragility. I have had to track down many a fun bug due to +load surprises over the decades. In general, it should be avoided. And when used, it should touch a minimal amount of the rest of the system specifically because you'll be changing the order within which things are initialized at runtime.
I would recommend that you have some kind of initialization hook in your framework that your framework's clients are expected to call in the app, typically during the app delegate's didFinishLaunching:.... method.
You can put in assert()s along other code paths that can warn or raise if the framework was not properly initialized.
I have an app with a Today extension. Using App Groups, I am able to have a single repository for both the app and the extension. However, I must be missing something as the solution only partially works. If I am in the App I can add a record and will see that same record in the widget. However, if a change the value of a column, for example, setting a boolean from true to false. The app won't see the change if it were made in the extension and vice versa. I am saving the change to Core Data:
_record?.managedObjectContext?.save()
Using DB Browser, I am able to verify that the change was made; is in the DB. Clearly, I am missing something. Any ideas would be appreciated.
Make sure you're using the same db in both sides
- (void)viewDidLoad {
[super viewDidLoad];
if ([self.extensionContext respondsToSelector:#selector(setWidgetLargestAvailableDisplayMode:)]) { // iOS 10+
[self.extensionContext setWidgetLargestAvailableDisplayMode:NCWidgetDisplayModeExpanded];
} else {
self.preferredContentSize = CGSizeMake(0, 110.0); // iOS 10-
}
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreAtURL:[MagicalRecord urlAppGroupStore]];
}
As you can see i'm using magical record but I'm specifying to use the share sqlite file
NSString *const kAppGroupIdentifier = #"group.com.carlosduclos.myapp";
+ (NSURL *)urlAppGroupStore {
NSURL *groupURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:kAppGroupIdentifier];
return [groupURL URLByAppendingPathComponent:#"mydb.sqlite"];
}
I'm trying to implement app to phone calling from Sinch for IOS. I have successfully implemented app to app calling which means that the SDK is properly set up in my app. However the app to phone for some reason is not working:
In my MainViewController.m, I have the following:
- (id<SINClient>)client {
return [(JSQAppDelegate *)[[UIApplication sharedApplication] delegate] client];
}
Then, in an alertView:
if ([self.client isStarted]) {
id<SINCall> call = [self.client.callClient callPhoneNumber:#"+46000000000"]; //Testing the phone number as advised by Sinch!
[self showCallController:call appToAppCall:NO];
}
Which call the following method:
-(void)showCallController: (id<SINCall>)call appToAppCall: (BOOL)appToapp
{
MMLCallingViewController *callView = [self.storyboard instantiateViewControllerWithIdentifier:#"CallingView"];
callView.call = call;
callView.callReceiverName = _theRecipient;
callView.calleeID = _theRecipientId;
if (!appToapp) {
callView.typeOfCall = #"phone";
callView.callStateLbl.text = #"Phone";
}
else {
callView.typeOfCall = #"app";
}
[self presentViewController:callView animated:YES completion:nil];
}
Now each time, I tried to test the call - I have the following problem:
The call view show, then stop suddenly with the below message showing in the console:
[appDelegate client:logMessage:area:severity:timestamp:] [Line 590] virtual void rebrtc::SetSDPObserver::OnFailure(const std::string &)Failed to set remote answer sdp: Offer and answer descriptions m-lines are not matching. Rejecting answer.
[appDelegate client:logMessage:area:severity:timestamp:] [Line 590] Failed to set remote answer sdp: Offer and answer descriptions m-lines are not matching. Rejecting answer.
My Sinch SDK version is: 3.7.1-0313526 - with Xcode 6.3 - IOS SDK 8.3
My Parse SDK version is 1.7.4
I have tried to look everywhere and read all the tutorials on Sinch! website about calling, and cannot find any clue for help.
Any help will be greatly appreciate.
Thanks in advance.
I'm trying to test out the iOS 8.1 handoff feature with NSUserActivity between my iPhone and my iPad. For this, I tried both implementing my own solution, and to use Apple's PhotoHandoff project. However, it's not working.
If I provide a webpageURL, the handover works fine, but when I try to use userData or addUserInfoEntriesFromDictionary nothing works, and I can't for the life of me figure out what the catch is to make the data work.
Sample code:
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"com.company.MyTestApp.activity"];
activity.title = #"My Activity";
activity.userInfo = # {};
// activity.webpageURL = [NSURL URLWithString:#"http://google.com"];
self.userActivity = activity;
[self.userActivity becomeCurrent];
[self.userActivity addUserInfoEntriesFromDictionary:# { #"nanananan": #[ #"totoro", #"monsters" ] }];
(I'm also unable to make it work with a Mac app with a corresponding activity type)
I hope you found the solution already, but in case somebody stumbles upon this problem too, here is a solution. (Actually not very different from the previous answer)
Create user activity without userInfo, it will be ignored:
NSUserActivity *activity = [[NSUserActivity alloc] initWithActivityType:#"..."];
activity.title = #"Test activity";
activity.delegate = self;
activity.needsSave = YES;
self.userActivity = activity;
[self.userActivity becomeCurrent];
Implement the delegate to react to needSave events:
- (void)userActivityWillSave:(NSUserActivity *)userActivity {
userActivity.userInfo = #{ #"KEY" : #"VALUE" };
}
When needsSave is set to YES this method will be called and userActivity will be updated.
Hope this helps.
To update the activity object’s userInfo dictionary, you need to configure its delegate and set its needsSave property to YES whenever the userInfo needs updating.
This process is described in the best practices section of the Adopting Handoff guide.
For example, with a simple UITextView, you need to specify the activity type ("com.company.app.edit") identifier in the Info.plist property list file in the NSUserActivityTypes array, then:
- (NSUserActivity *)customUserActivity
{
if (!_customUserActivity) {
_customUserActivity = [[NSUserActivity alloc] initWithActivityType:#"com.company.app.edit"];
_customUserActivity.title = #"Editing in app";
_customUserActivity.delegate = self;
}
return _customUserActivity;
}
- (void)textViewDidBeginEditing:(UITextView *)textView
{
[self.customUserActivity becomeCurrent];
}
- (void)textViewDidChange:(UITextView *)textView
{
self.customUserActivity.needsSave = YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView
{
[self.customUserActivity invalidate];
return YES;
}
- (void)userActivityWillSave:(NSUserActivity *)userActivity
{
[userActivity addUserInfoEntriesFromDictionary:#{ #"editText" : self.textView.text }];
}
FWIW, I was having this issue. I was lucky that one of my Activity types worked and the other didn't:
Activity: Walking
(UserInfo x1,y1)
(UserInfo x2,y2)
(UserInfo x3,y3)
Activity: Standing
(UserInfo x4,y4)
Activity: Walking
etc.
I got userInfo if the handoff occured when standing but not walking. I got other properties such as webpageURL in all cases; just userInfo came through null.
The fix for me was to invalidate & recreate the NSUserActivity object every time (e.g. when Walking to x2/y2 from x1/y1), instead of only when Activity type changed (e.g. from walking to standing). This is very much not the way the doc is written, but fixed the issue on iOS 9.
UPDATE: This workaround doesn't work on iOS 8. You need to implement this via the userActivityWillSave delegate, as gregoryM specified. Per Apple's doc:
To update the activity object’s userInfo dictionary efficiently,
configure its delegate and set its needsSave property to YES whenever
the userInfo needs updating. At appropriate times, Handoff invokes the
delegate’s userActivityWillSave: callback, and the delegate can update
the activity state.
This isn't a "best practice", it is required!
[Note: issue occurred on iOS 9 devices running code built on Xcode 6.x. Haven't tested Xcode 7 yet, and issue may not occur on iOS 8.]
I am using a package called BlackRaccoon to upload files to an FTP server. I'm really struggling with a really basic delegation concept that I just cant figure out.
In my uploading class .m file, I have a method which calls the BlackRaccoon uploading request. I use the following code:
uploadFile = [BRRequestUpload initWithDelegate: self];
uploadFile.path = #"/filehere.txt";
uploadFile.hostname = #"xxx";
uploadFile.username = #"xxx";
uploadFile.password = #"xxx";
//we start the request
[uploadFile start];
And in my .h file i have the following:
#interface myClass : NSObject <BRRequestDelegate>
{
BRRequestCreateDirectory *createDir;
BRRequestDelete * deleteDir;
BRRequestListDirectory *listDir;
BRRequestUpload *uploadFile;
NSData *uploadData;
}
Every time I compile and run, I get an error somewhere in the BlackRaccoon files such as:
__25-[BRStreamInfo openRead:]_block_invoke [Line 190] No response from the server. Timeout.
On sourcing this error, I find myself in the method creating the read stream, particularly:
request.didOpenStream = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout * NSEC_PER_SEC), dispatch_get_local_queue(), ^{
if (!request.didOpenStream && request.error == nil)
{
InfoLog(#"No response from the server. Timeout.");
request.error = [[BRRequestError alloc] init];
request.error.errorCode = kBRFTPClientStreamTimedOut;
[request.delegate requestFailed: request];
[request.streamInfo close: request];
}
});
I am totally flunked by this. Our server is up and running and has no timeout issues with our android devices or apps such as 'FTP Sprite'.
Im pretty sure the issue lies with the initWithDelegate stuff. I just dont think im delegating correctly, but Im not sure why. Does anybody have any ideas? I know I am in need of (id)init somewhere, but I just dont understand where or how.
Thanks!
Just checked the uploadFile function at BlackRaccoon Git, and I found that in their example, they don't alloc a BRRequestUpload object, but call it like this :
uploadFile = [BRRequestUpload initWithDelegate: self];
Might this be the cause of your crash?
Please make one line change in BRRequest.m file.
In - (id)initWithDelegate:(id<BRRequestDelegate>)aDelegate Function.
Change
self.passiveMode = YES;
to
self.passiveMode = NO;
And it will work....
That change worked for me.