I have a table view showing records
When user taps ona record I need to show the details of that record
The detail shown also includes an image that I need to download from awebserver ( GIF ) file
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection release];
[request release];
NSData *imageData = [NSData dataWithContentsOfURL:url];
The problem is the last line where it takes some time to download the image
I am wondering what the possible options should be / can be done
I know the initWithRequest is aynchronous - but the call to dataWithContentsofUrl is right in the main path - so user experiences a wait time of more than a second
Is there a way that I could possibly have the dataWithContentsofUrl pushed into some other method that is called asynchronously
If thats possible I could look at showing a spinner or "please wait...." while the image loads
The other option I am thinking of is :
Should I have the images saved on the server in a zipped format ?
and then unzip ?
will it be faster ?
has anyone tried it before ?
or does anyone have a better option ?
I am not looking for spoon feeding but would like to hear your ideas please!
Related
I have been searching stackoverflow and Googling for hours. I made a simple project to mess around with Core Data and bindings. It added an entity to the model and it wouldn't work any more. I was getting "This NSPersistentStoreCoordinator has no persistent stores It cannot perform a save operation" whenever I tried to add data to a new document. I followed every piece of advice I could find with no luck.
Finally, I made a new project (NSPersistentDocument based) and I made a new model from scratch. I made sure the model was perfect before I ran the project for the first time.
In WindowControllerDidLoadNib: The project calls a method to add data. Before addData routine, I log the ManagedObjectContext and the ManagedObjectModel. Neither of them are nil.
I am still getting this %$&##! error.
Does anyone have any new ideas about this?
EDIT: Could this be because the new untitled document has never been saved? If so, how do you get around that? Can you save an untitled document? Do you really want to?
I had a similar problem a while back on a file import. Since I had full control, I named and saved the document and then I was able to save the context.
As I indicated in the comment above, at least in Mountain Lion you have to save the document at least once before you can save the context. I did some experiments and the small amount of data that I changed was preserved by autosave without saving the context. I have changed my saveContext method to the following:
- (void)saveContext {
if (![self fileURL]) {
NSLog(#"Can't save context. No file name has been set.");
return;
}
NSError *error = nil;
if (![self.managedObjectContext save:&error]) {
[NSApp presentError:error];
NSLog(#"Error userInfo: %#",[error userInfo]);
abort();
}
}
First off, I should mention that this is my first post on this site. I am trying to teach myself to program iOS and in my google searches for answers I find that I'm constantly directed here. So thank you to all who have contribute here. You have help me a ton already.
I have been going through the Stanford CS193P class and LOVE it. But I'm stuck right now and not sure where to turn.
My problem has been with the UIManagedDocument.
I tired to make a simple app to test my new skills. This is what it does:
A simple accounting app that tracks individual contributions to a fundraising event.
I have a UITabBar that on each tab allows you to:
1. Track the participants (Players) - This will connect to the address book and allow you to add them or just keep them in this app.
2. Manage the events (Events) - You can add, edit or delete events that you will then add participants to and then be able to add what they brought (Bank) in on that event.
3. Settings. - I've added some buttons just to help me figure stuff out now including a reset button that clears all data and a "dummy data" button.
I have three coreData Entities. Players, Events and Bank each with relationships with the other two.
When I first tried to make this app (pre iOS5) I used the appDelegate to create my ManagedObjectContext and pass it around to my viewControllers. That worked. But now I'm supposed to use the UIManagedObjectDocument and not use the AppDelegate. I believe I understand the principle and the integration to iCloud. (I could be wrong)
Using the examples from the class and what I could find online I made a helper class that will provide the first of each of my ViewControllers within my UINavigationControllers the ManagedDocument.
+ (UIManagedDocument *)sharedManagedDocument
{
static UIManagedDocument *sharedDocument = nil;
NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
url = [url URLByAppendingPathComponent:#"DefaultAppDatabase"];
// url is "<Documents Directory>/<DefaultAppDatabase>"
// Create the shared instance lazily upon the first request.
if (sharedDocument == nil) {
sharedDocument = [[UIManagedDocument alloc] initWithFileURL:url];
}
if (sharedDocument.fileURL != url) {
UIManagedDocument *newDocument = [[UIManagedDocument alloc] initWithFileURL:url];
sharedDocument = newDocument;
}
NSLog(#"SharedDocument: %#", sharedDocument);
return sharedDocument;
}
I then had that first ViewController open the document and perform the fetch.
From there I pass whatever NSManagedObject is selected to the next ViewController through the segue.
The problems are when I get to adding or reseting the data. (I'm assuming that if I can get it to work with the "dummy data" button I can get it to work on an individual entry) When I press the "Reset" or "Dummy" button my logs tell me that it was pushed but I don't see any change in the data until I restart the app. Then it shows up perfectly. My guess is I'm not saving the file correctly or I'm not refreshing the tableViews correctly. I made a small attempt at using NSNotification but didn't go to far into it since I couldn't get it to respond to anything. I'm happy to go back down that road if I need to.
This is my save method... pretty much just copied from the default coreData appDelegate.
- (void)saveContext
{
NSError *error = nil;
NSManagedObjectContext *managedObjectContext = self.appDatabase.managedObjectContext;
if (managedObjectContext != nil)
{
if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error])
{
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
[self.appDatabase saveToURL:self.appDatabase.fileURL
forSaveOperation:UIDocumentSaveForOverwriting
completionHandler:^(BOOL success){
if(!success) NSLog(#"failed to save document %#", self.appDatabase.localizedName);
if(success) NSLog(#"Success: save document %#", self.appDatabase.localizedName);
}];
}
Paul, the instructor from CS193P, suggested in Assignment 6 to use a helper method to pass around the UIManagedDocument through a block. I get the theory behind blocks but haven't completely wrapped my head around them so I haven't ruled out that my answer may lie there as well.
Thank you so much for any help or pointing me in the right direction to do more research. Sorry this post is so long, I was trying to be as clear as I can.
I was over thinking the whole thing. Alan asked my question perfectly in this post:
How do I create a global UIManagedDocument instance per document-on-disk shared by my whole application using blocks?
The question and the answers cleared everything up.
Thanks
The following code is fetch data (fill data for the first time) part of my tableViewController. I am using an NSManagedDocument's managedObjectContext to fill (pre populate) my database. The source is an array that I clean up from my TXT file which rests directly in the Xcode's resources folder. After this creation, I have document cases like closed / open and normal.
The following code inputs and fetches my data onto the table correctly with a fetched results controller request. However, when the data is loading in the thread that is meant to free the UI from this one time data creation (26854 object names) into managedObject.name attribute heavy operation, the tableview and my UI is frozen (for 1-15 seconds that is I think while populating in document.managedObjectContext for the first time for my database).
After 10-15 seconds data is loaded and shows correctly. However, when I stop the simulator and restart the app in simulator, although I save the document as seen in below code, and I use the same fetch results controller setup (and request) the table view shows empty, it is movable in this case (The document state shows open and normal at this stage and file path is same, I checked... It seems like neither autosave nor explicit saveForOverwriting I use work... Or is it something else? I tried a lot of things and I'll go crazy soon. I think it has something to do with my multithreading.
self.managedObjectNames is the array property in the table view and I set it from the TXT file during my table view's loadView:
Is there anybody out there who can show the mistake here? Is it that I give self.managedObjectNames in the method of entity creation category.
Thanks!
- (void)fetchDataIntoDocument:(UIManagedDocument *)document {
dispatch_queue_t fetchQ = dispatch_queue_create("Data fetcher", NULL);
dispatch_async(fetchQ, ^{
[document.managedObjectContext performBlock:^{
for (int i = 0; i < 26854; i++) {
[managedObject managedObjectWithId:[NSNumber numberWithInt:i] andArray:self.managedObjectNames inManagedObjectContext:document.managedObjectContext];
}
// NSLog(#"Save baby!!?");
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:nil];
}];
});
dispatch_release(fetchQ);
}
The reason why your UI is blocked for 10-15 seconds is because the document.managedObjectContext has been created with NSMainQueueConcurrencyType. That means that the performBlock: method will be executed on the main queue.
Creating the fetchQ in your code does not have any reason. It would have a reason if fetching of data would take some considerable amount of time but adding them would be fast (e.g. creating/modifying only few objects):
dispatch_async(fetchQ, ^{
// fetch data here (e.g. fetchAttribute may take few seconds)
NSString *attribute = fetchAttribute();
[document.managedObjectContext performBlock:^{
MyObject *o;
o = [NSEntityDescription insertNewObjectForEntityForName:#"MyObject"
inManagedObjectContext:document.managedObjectContext];
o.myAttribute = attribute;
}];
});
However I don't know answer to your main question.
In an iOS app, I need to provide image filters based on their size (width/height), think of something similar to "Large, Medium, Small" in Google images search. Opening each image and reading its dimensions when creating the list would be very performance intensive. Is there a way to get this info without opening the image itself?
Damien DeVille answered the question below, based on his suggestion, I am now using the following code:
NSURL *imageURL = [NSURL fileURLWithPath:imagePath];
if (imageURL == nil)
return;
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithURL((CFURLRef)imageURL, NULL);
if(imageSourceRef == NULL)
return;
CFDictionaryRef props = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);
CFRelease(imageSourceRef);
NSLog(#"%#", (NSDictionary *)props);
CFRelease(props);
You can use ImageIO to achieve that.
If you have your image URL (or from a file path create a URL with +fileURLWithPath on NSURL) you can then create an image source with CGImageSourceCreateWithURL (you will have to bridge cast the URL to CFURLRef).
Once you have the image source, you can get a CFDictionaryRef of properties of the image (that you can again bridge cast to NSDictionary) by calling CGImageSourceCopyPropertiesAtIndex. What you get is a dictionary with plenty of properties about the image including the pixelHeight and pixelWidth.
You can pass 0 as the index. The index is because some images might have various embedded images (such as a thumbnail, or multiple frame like in a gif).
Note that by using an image source, the full image won't have to be loaded into memory but you will still be able to access its properties.
Make you you import and add the framework to your project.
Just one addition:
if you want to use web-url like http://www.example.com/image.jpg you should use then
[NSURL URLWithString:imagePath] instead [NSURL fileURLWithPath:imagePath].
I see this issue only on the iPad. The same things works as expected on the iPhone.
I am opening the URL from my application in a UIWebView. If the URL is a normal web page, it works fine as expected. But if the URL is that of a remote video/audio file, the UIWebView opens the default player which is again good.
Now when I dismiss the UIWebView (by clicking on the Done button on the player), the streaming doesn't stop and the audio/video keeps playing in the background (I cannot see it but it does keep playing in the background, can hear it). The UIViewController in which the webview was created is also dealloced (I put in a log statement in the dealloc method) but the streaming doesn't stop.
Can someone please help me out on why this could be happening? And how can I stop the audio/video streaming when the UIWebView is closed?
Thanks.
I have the same issue as stated but in this case the video that won't stop playing is a Youtube video embeded using the object/embed method.
I spent a long time trying to figure out how to get the video to stop playing and the only solution I found was to tell the UIWebView to load a blank page before dismissing the view:
[self.webContent loadRequest:NSURLRequestFromString(#"about:blank")];
Edit(2015-05-12): As mentioned by #chibimai below, this answer by alloc_iNit works along the same lines but since my answer is from 5 years ago -- and his only 4 -- the linked answer may be more applicable. I no longer do iPhone dev work so I cannot determine which is better either way.
I also found this behaviour simply because the web view hadn't been released.
I know this is pretty old thread, but for the sake of new developers like me.
My scenario was: I was using split controller, with media list on master and player in the detail controller section
I faced the same issue and tried all sorts of workaround.
I finally figured it out that the problem was simple, I was holding the reference of the detail controller in my master, and was not releasing the earlier detail controller. A simple release in the master at the right place solved the issue.
I'm working on the same problem. I've found that if you define something like this in your script tag:
function stopVideo(){ video.pause(); }
window.onunload = stopVideo;
Then in your UIViewController, add in:
-(void)viewWillDisappear:(BOOL)animated{
[webView stringByEvaluatingJavaScriptFromString:#"window.onunload();"];
[super viewWillDisappear:animated];
}
It seems to try to pause/stop the video for several seconds, but then you hear the audio continue to play!
Update!
This is a general bug with the media player. You have to set the playback time to -1 in order to make it really stop.
I've just had an issues with an mp4 file opening automatically in media player from a web-page without possibility to close it - "Done" button was only seen in video fullscreen mode and just closed fullscreen, so I had no way to return to the webpage without adding a "Back" button to the navigation bar. (on iPhone the video was opening in a separate view with "Done" button taking back to the WebView with the source page)
The workaround that helped me is to open the video file in a separate media player.
catch opening an MP4 file
- (BOOL) webView: (UIWebView *) webView shouldStartLoadWithRequest: (NSURLRequest *) request navigationType: (UIWebViewNavigationType) navigationType
{
NSRange range = [request.URL.absoluteString rangeOfString: #".mp4" options: NSCaseInsensitiveSearch];
if ( range.location != NSNotFound ) //opening MP4 video file
{
[self showFullscreenMediaWithURL: request.URL];
return NO;
}
return YES;
}
where
- (void) showFullscreenMediaWithURL: (NSURL *) mediaURL
{
MPMoviePlayerViewController *ctrl = [[MPMoviePlayerViewController alloc] initWithContentURL: mediaURL];
ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController: ctrl animated: YES];
[ctrl release];
}
opens video with URL in a media player in a model view
don't forget to add MediaPlayer.framework to the project and import
#import <MediaPlayer/MediaPlayer.h>
for the project to be built
PS. many thanks to Viktor Gubrienko for the solution
In fact, I prefer to use the null link to solve the problem.
like this:
[self.webView loadRequest:NSURLRequestFromString(#"about:blank")];
thanks to your thread on this issue I was able to figure out a solution that resolves the issue completely, and I tested it on my IPad, not just the simulator. This also resolves the issue with the audio playing. This is not the permanent resolution but an effective work around.
Overview of the approach:
Basically all that is needed is to send a short audio file to the webView. I made a copy of the IMac submarine.m4v file, I called ping.m4v and added it to my xcode project in the resource folder.
Then at the point when I want the video / audio to stop I do the following steps:
webView.hidden = TRUE;
NSString *mimeType = [[NSString alloc] initWithString:#"video/x-m4v"];
NSData *PingData = [NSData alloc];
PingData = [NSData dataWithContentsOfFile:PingFilePath];
[webView loadData:PingData MIMEType:mimeType textEncodingName:nil baseURL:[NSURL URLWithString:PingFilePath]];
Now you also have to handle error "204". Error 204 seems to be just a warning saying that webView is going to handle this audio file. To handle the error I added this single line (see >>>) to didFailLoadWithError
(void)webView:(UIWebView *)BHwebView didFailLoadWithError:(NSError *)error
{
NSLog(#"Error %i", error.code);
if (error.code == NSURLErrorCancelled) return; // this is Error -999
if (error.code == 204) return; // this is Error 204 - for audio player in webview.
Finally I turn webView.hidden = FALSE right before I display my next Audio/Video. This way the little play bar for the sound file does not show on the display. There is just a soft ping sound... any audio file will do...
I hope this helps...
This is what I use. It does log this error "Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session." but does stop the audio file from playing (fading out) and remembers the play position.
AVAudioSession *s = [AVAudioSession sharedInstance];
if (s != nil)
[s setActive:NO error:nil];
As web view hadn't been released, the video keeps playing on the web view in background.
So you need to release the web view simply by using below code which works fine for me.
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:(BOOL)animated];
[self.wvwebview removeFromSuperview];
}
I hope this helps...
in my case using loadHTMLString and not loadRequest with #"about:blank", fix the problem on macOS
[self.wkWebView loadHTMLString:#"about:blank" baseURL:nil];
I had the same issue in Table view where the table view cell has a webview which loads an audio file . Once you play and go back to another screen the audio still plays .
Solution : reload the table view in viewWillDisappear(_:) of the table view controller and the audio will stop playing once you navigate to another screen .