How to populate a UITableView from a remote .plist file - objective-c

Sorry if this has been asked many times before, but nothing seemed to help my issue.
I have set up my app to work perfectly reading from local .plist files from within the app itself for testing purposes, but for the app to be of any use, I need to be able to update it on a web server and load it remotely.
Here is my current code for local plists.
How would I amend this with minimum changes elsewhere in the app?
Many thanks in advance.
- (void)viewDidLoad
{
[super viewDidLoad];
//Create a dictionary with the contents of Squad.plist
NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"Squad" ofType:#"plist"]];
//Fill the Squad array with contents of dictionary under the key "Players"
[self setSquad:(NSArray*) [dictionary objectForKey:#"Players"]];
//Reload the table data
[[self tableView] reloadData];
[[self navigationItem] setPrompt:nil];
}

You will need to first download your remote .plist while showing a loading or something, and after it's downloaded, use it locally the same way you are doing now. There's no way to read it remotely.

Related

How to create .plist file under /Library/LaunchAgents

I'm trying to develop a launch agent for macOS via Apple Doc
https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html
One of my requirements is that the agent should work for all users. What I understood from above document is I have to put my .plist under "/Library/LaunchAgents" folder.
When I try to create this file programatically nothing happens with the below code.
NSMutableDictionary *plist = [[NSMutableDictionary alloc] init];
[plist setObject:#"test" forKey: #"test 1"];
NSString *userLaunchAgentsPath = [[NSString alloc] initWithFormat:#"%#", #"/Library/LaunchAgents/com.xxx.agent.plist"];
[plist writeToFile:userLaunchAgentsPath atomically:YES];
Probably the reason is a privilege issue. Do you have any ideas for solving this issue?
As to privileges, the plist should be owned by root and if you want the app to run as a different user, you can do that easily by providing the username/password in the plist. Your app is probably not running as root.

Write NSDictionary to ONLINE .plist. Is it possible?

I'm writing an app for instant messaging aaand I'm stuck.
I am able to read data (dictionary) from plist that's on my dropbox, but I can't modify it from my app, what is a thing I actually want to achieve.
Here is how I read the online .plist file:
#Implementation
NSDictionary *wholePlist;
-(void)viewDidLoad
{
wholePlist = [[NSDictionary alloc]initWithContentsOfURL: [NSURL URLWithString:[NSString stringWithFormat:#"https://dl.dropboxusercontent.com/s/cfpree9see19t00/users.plist"]]];
self.allUsers = [wholePlist objectForKey:#"allUsers"];
} //self.allUsers is NSDictionary, also.
And this is how I am trying to save it if I change it
- (IBAction)registerButtonPressed:(UIButton *)sender {
NSString *username = self.usernameTextField.text;
NSString *password = self.setPassTextField.text;
NSMutableArray *myContacts = [[NSMutableArray alloc]init];
NSMutableArray *inbox = [[NSMutableArray alloc]init];
NSDictionary *user = [[NSDictionary alloc]initWithObjects:#[username, password, myContacts, inbox] forKeys:#[#"username",#"pass",#"myContacts",#"inbox"]];
if ([user isEqualToDictionary:[self.allUsers objectForKey:username]]) {
[[[UIAlertView alloc]initWithTitle:#"Registration error" message:#"Username already taken. Please, choose another username." delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil]show];
} else {
NSURL *plistURL = [NSURL URLWithString:[NSString stringWithFormat:#"https://dl.dropboxusercontent.com/s/cfpree9see19t00/users.plist"]];
[self.allUsers setValue:user forKey:username];
[self.allUsers writeToURL:plistURL atomically:YES];
}
}
If I do it locally/offline (in some folder inside my Mac or app directory) using writeToFile: it works. When I use writeToURL: it doesn't work.
My questions are:
Is this even possible, what am I trying to achieve?
Is it possible with any other storage client?
If it's possible with some other storage client, please give me source link on how to OR explain how to.
Thanks!
Instant messaging applications are almost always best done using sockets. I'd HIGHLY recommend against using a file on a server to read and write from. While it's possible, you're asking for a world of pain and slugish-ness.
So to answer your questions in a striaght forward manner:
Yes... Don't do it.
Yes. of course you can use CloudKit either the DB or file upload part. Again, I recommend against this method because it's slow and has high overhead on the network.
I highly recommend reading up on sockets: http://en.wikipedia.org/wiki/Network_socket to better understand this approach. I have a chat socket written in C++. However it does a bit more than what you may need: https://github.com/theMonster/ModularServer. Also, there's a very popular chat server example for node.js here: http://socket.io/get-started/chat/
Let me know if you have any questions.

iCloud NSMetadata query results are blank

I've been working on adding icloud to my project (which is quite a pain in the buns) and I'm able to save and remove files, but I can't get a list of the files stored in iCloud. I've tried solutions from about 10 different websites (including the Apple documentation). Whenever I call [self.query startQuery]; everything seems to be working: The correct methods get called, the methods execute exactly as they should. Then when I ask for an nsarray of the files in my app's iCloud Documents directory I get two parenthesis with nothing between them (when I view in NSLog): File List: ( ). I know for a fact that there are many different documents of all shapes, extensions, sizes, and names in my app's iCloud Documents directory because I've been using the iCloud Developer site to check if things are working. My first method to setup the query is as follows:
- (void)syncWithCloud {
self.query = [[NSMetadataQuery alloc] init];
NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
[query setSearchScopes:[NSArray arrayWithObjects:NSMetadataQueryUbiquitousDocumentsScope, nil]];
//[query setPredicate:[NSPredicate predicateWithFormat:#"%K LIKE '*'", NSMetadataItemFSNameKey]];
[query setPredicate:[NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%%K like \"%#*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey]];
//Pull a list of all the Documents in The Cloud
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processFiles:)
name:NSMetadataQueryDidFinishGatheringNotification object:self.query];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(processFiles:)
name:NSMetadataQueryDidUpdateNotification object:self.query];
[self.query startQuery];
}
The process files method provided by Apple is next:
- (void)processFiles:(NSNotification*)aNotification {
NSMutableArray *discoveredFiles = [NSMutableArray array];
//Always disable updates while processing results.
[query disableUpdates];
//The query reports all files found, every time.
NSArray *queryResults = [query results];
for (NSMetadataItem *result in queryResults) {
NSURL *fileURL = [result valueForAttribute:NSMetadataItemURLKey];
NSNumber *aBool = nil;
// Don't include hidden files.
[fileURL getResourceValue:&aBool forKey:NSURLIsHiddenKey error:nil];
if (aBool && ![aBool boolValue])
[discoveredFiles addObject:fileURL];
}
//Update the list of documents.
[FileList removeAllObjects];
[FileList addObjectsFromArray:discoveredFiles];
//[self.tableView reloadData];
//Reenable query updates.
[query enableUpdates];
NSLog(#"File List: %#", FileList);
}
Why doesn't this give me a list of files or at least some kind of data? Am I defining the NSMetadata query wrong, maybe my predicate isn't formatted right? I know I'm doing something wrong because there's no way iCloud could be this complicated (or could it?).
Thanks for the help in advance!
Edit #1: I am continuing to try different approaches to this problem. Using one of the answers below I have changed the predicate filter as follows:
[query setPredicate:[NSPredicate predicateWithFormat:#"NSMetadataItemFSNameKey LIKE '*'"]];
I have also added the following lines before the [query enableUpdates] call:
for (NSMetadataItem *item in query.results) {
[FileList addObject:[item valueForAttribute:NSMetadataItemFSNameKey]];
}
In the processFiles method, I've tried placing all of the code on the background thread, but this makes no difference - as a matter of fact, when the code is not executed on the background thread FileList gives me this (null) instead of this ( ).
Could my problem have to do with thread management or memory allocation? Please note that I am using ARC.
Edit #2: The FileList variable is an NSMutableArray defined in my #interface and initialized in the -(id)init method before calling the processFiles method.
Edit #3: When testing my code with breakpoints I found that the following for-loop never gets run through - not even once. This leads me to believe that:
A. The proper directory isn't being connected with
B. iCloud can't see the files in the directory
C. My NSMetadataQuery isn't being setup properly
D. Something completely different
Here's the code that starts the for-loop which never gets run:
NSArray *queryResults = [query results];
for (NSMetadataItem *result in queryResults) {
Since you already set the search scope correct, there's no need to use special filters in the predicate.
Just use:
query.predicate = [NSPredicate predicateWithFormat:#"NSMetadataItemFSNameKey == '*'"];
And to get the array use:
NSMutableArray *array = [NSMutableArray array];
for (NSMetadataItem *item in query.results) {
[array addObject:[item valueForAttribute:NSMetadataItemFSNameKey]];
}
I've solved my problem. Getting the list of files in iCloud was just a matter of correctly defining, allocating, and initializing properties. SAE's answer and this SO posting helped me solve my problem and create this GitHub Repo called iCloud Document Sync. The iCloud Document Sync class simplifies the whole iCloud Document storage process down to a few lines of code. The commit linked here fixes the issues from my question.

Drag Files come across Sandbox(__CFPasteboardIssueSandboxExtensionForPath)

I processed drag operation from browser view to custom view.It work well in snow lepoard,but not in Mountain Lion with sandbox.
in browser view:
NSMutableArray* urls = [[[NSMutableArray alloc] init] autorelease];
..............put some NSUrl to urls array....................
[pasteboard writeObjects:[NSArray arrayWithArray:urls]];
in my receive custom view:
NSArray* pasteboardItems = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSString class]] options:nil];
NSArray* pasteboardItems2 = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSURL class]] options:nil];
NSArray* pasteboardItems3 = [pasteboard readObjectsForClasses:[NSArray arrayWithObject:[NSImage class]] options:nil];
NSLog(#"%#",pasteboardItems);
NSLog(#"%#",pasteboardItems2);
NSLog(#"%#",pasteboardItems3);
my log is:
2012-08-09 18:33:43.886 iCollage[6885:303] __CFPasteboardIssueSandboxExtensionForPath: error for [/Users/xxxx/Library/Containers/xxxxxxxxxxxx/Data/Downloads/1343902069.jpg]
2012-08-09 18:33:44.546 iCollage[6885:303] ( "file://localhost/Users/xxx/Library/Containers/xxxxxxxx/Data/Downloads/1343902069.jpg")
2012-08-09 18:33:44.547 iCollage[6885:303] ( "file://localhost/Users/xxxxx/Library/Containers/xxxxxx/Data/Downloads/1343902069.jpg")
2012-08-09 18:33:44.547 iCollage[6885:303] ()
my question is:
1.how to fix this error __CFPasteboardIssueSandboxExtensionForPath;I refer the docs and found nothing about that.I am ensuer that i have the permission to access the file!google says, may be "startAccessingSecurityScopedResource" will help me, then i try and failed
2.why pasteboardItems2 have value?i write to pasteboard only url but not string.It disgusted me that I can get the url both from NSString type and NSUrl type! (I try drag a file from iFinder, the url will only exist in pasteboardItems but not pasteboardItems2).Anybody know why? I think the first problem will auto fixed when some one help me fix this problem.
I believe Apple answer question 1:
Important: Although you can support dragging file paths, in general,
you should avoid doing so unless you are certain that the destination
app will never be run in an app sandbox. If you use an NSString, OS X
has no way to know whether that string should be interpreted as a
path; thus, OS X does not expand the destination app’s sandbox to
allow access to the file or directory at that location. Instead, use
an NSURL, a bookmark, or a filename pasteboard type.
WRT to question 2, it looks like you have pasted URLs so reading NSURL objects would seem to be correct. However I think you should implement the dragging using the following code (also from the link above):
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
NSPasteboard *pboard = [sender draggingPasteboard];
if ( [[pboard types] containsObject:NSFilenamesPboardType] ) {
NSArray *files = [pboard propertyListForType:NSFilenamesPboardType];
int numberOfFiles = [files count];
// Perform operation using the list of files
}
return YES;
}
You need to generate security-scoped URL bookmark data on the sender side, and turn that data back into a URL on the receiver side. There's some other stuff you have to do after that when you want to actually access the URL; the documentation elaborates.
The receiving application, when running in a sandbox, will not be able to handle bare paths. This is a core part of being sandboxed; you are not allowed to use bare paths or their corresponding URLs to access files that aren't in your sandbox container and haven't been explicitly handed to you by the user.
Your pasteboardItems read object of NSString type, but you dragged a file(with jpg extension), you should register for NSString type in your init method:
[self registerForDraggedTypes:[NSArray arrayWithObject:NSPasteboardTypeString]];
You need to have Document Types defined in your application so that the sandboxing mechanism knows your application should be opening files with those extensions. You can do this by clicking the project on the left in Xcode, and in the Info tab, under Document Types add a new document type for each extension.
You just need to fill in the name and extensions field.
Also if you want to persist your permission to access the files dragged onto your application, you can use this class to wrap up all that logic. https://github.com/leighmcculloch/AppSandboxFileAccess

reloadRowsAtIndexPaths stops loading UITableViewCells?

I'm trying to incorporate favicons into a UITableView. The table basically fetches websites, and I want to display the favicon onto the right. I put a placeholder icon at the right initially and let a function in the background run. This function takes the URL of the website, parses it and attempts to find the favicon. If it can't find it, it keeps the same placeholder image; otherwise, it replaces it with the site's favicon. I initially tried using [tableView reloadData] which worked well in the simulator, but it did really odd and unreliable things (like for instance, it would create some cells, but then leave a giant, blank cell). Anyway, I stumbled upon reloadRowsAtIndexPaths, and it seems like the function I need to use. However, the results are still pretty unreliable. I have my fetching function running in the background as such:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...//other code here for showing website labels
//obtain the favicon.ico
if(!this_article.iconLoaded){
this_article.iconLoaded = TRUE;
NSDictionary *args = [NSDictionary dictionaryWithObjectsAndKeys:this_article, #"article", indexPath, #"indexPath", nil];
[self performSelectorInBackground:#selector(fetchFaviconWrapper:) withObject:args];
}
cell.favicon.image = this_article.icon;
return cell;
}
in FetchFaviconWrapper:
- (void)fetchFaviconWrapper:(NSDictionary *)args {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self fetchFavicon:[args objectForKey:#"article"]];
NSArray *paths = [NSArray arrayWithObject:[args objectForKey:#"indexPath"]];
[articleTable beginUpdates];
[articleTable reloadRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
[articleTable endUpdates];
[pool release];
}
Basically, fetchFavicon takes a website, takes the host URL, appends "/favicon.ico", constructs it into an NSData object, and finds the image (if it exists). However, this has also been pretty unreliable. What would be the best way to replace the placeholder image while running a thread in the background? I could do everything on the main thread, but that just makes the table load slowly. There seems to be something that I'm overlooking, or something that I just forgot to add...just can't figure it out.
It is not entirely clear if you are accessing and modifying your UI from the separate thread.
If so, this is the cause of your unreliability. UIKit can be accessed only from the main thread. If you are interested you will find many questions on S.O. and many discussions on the web.
There is a workaround, if you want to keep your second thread. Indeed, you can send messages to your UI object using:
-performSelectorOnMainThread:withObject:waitUntilDone:
instead of sending them directly from the secondary thread.
If this workaround does not solve the issue for you, then I would suggest redesigning your app so that the secondary thread only access your model, without accessing the UI. All the operations that modify the UI should be executed on the main thread. If you need to call reloadData on your table when the model is ready, you can do it using performSelectorOnMainThread:withObject:waitUntilDone.
I'd suggest a few things.
Appending ./favicon.ico isn't always accurate. Look into various methods of adding favicons to sites to support them.
As far as replacing the default placeholder, I suggest using the NSNotificationCenter to inform the main thread when to make the changes.
I definitely reiterate the no UI from background thread.
change your initial code:
- (void)fetchFaviconWrapper:(NSDictionary *)args {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self fetchFavicon:[args objectForKey:#"article"]];
NSArray *paths = [NSArray arrayWithObject:[args objectForKey:#"indexPath"]];
[articleTable beginUpdates];
[articleTable reloadRowsAtIndexPaths:paths
withRowAnimation:UITableViewRowAnimationFade];
[articleTable endUpdates];
[pool release];
}
to this, and see if you're still having the problem
- (void)fetchFaviconWrapper:(NSDictionary *)args {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self fetchFavicon:[args objectForKey:#"article"]];
NSArray *paths = [NSArray arrayWithObject:[args objectForKey:#"indexPath"]];
[self performSelectorOnMainThread:#selector(reloadData)
withObject:nil
waitUntilDone:YES];
[pool release];
}
The waitUntilDone is irrelevant in this case, and it's preferred form to say YES.
Another problem to suspect when you have funky cell behavior is if you are correctly handling cell reuse. You didn't show us that code, so we can't tell. If you are keeping a reference to a cell somewhere and setting the image data into that - you'll hose yourself when the table reuses the cell out from under you. (I don't think that's the problem in this case.)