Sorry for the poorly worded title. I have a logic issue that I'm trying to get my head around. The view that I'm working in has a UICollectionView that displays a list of "tanks" associated with a user. This collection view displays a three items:
Tank Name
Tank Capacity
Last Image Stored
The last image stored part is where I'm having trouble. I'm making progress but its a matter of the logic behind it that I'm not sure on. Here is what the data looks like:
I have two classes that I'm interacting with; SavedTanks and SavedTankImages. The unique objectId from a saved tank is also stored as a value in SavedTankImages to allow a sort of pointer reference to the image. This logic works when the user loads a "tank" and can see all of the images they've stored associated with it.
However, for the purposes of this view, I only need to grab the first image from each tank and display that. This is where I need help. Here's what I have so far:
#pragma mark COLLECTION VIEW
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
photoHandler *cell = (photoHandler *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
_tankNameArray = [_array objectAtIndex:indexPath.section * 1 + indexPath.row];
cell.tankNameLabel.text = [_tankNameArray valueForKey:#"tankName"];
cell.tankCapLabel.text = [_tankNameArray valueForKey:#"tankCapacity"];
NSArray *objectId = [_array valueForKey:#"objectId"];
for (int i = 0; i < objectId.count; i++)
{
NSString *objectString = [[NSString alloc] init];
objectString = [objectId objectAtIndex:i];
PFQuery *imageQuery = [PFQuery queryWithClassName:#"SavedTankImages"];
[imageQuery whereKey:#"tankObjectId" equalTo:objectString];
[imageQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
for (PFObject *object in objects)
{
NSLog(#"OBJECT TEST: %#", object);
}
}
}];
}
return cell;
}
On OBJECT TEST: %#, this is the logged output:
2014-05-28 11:59:44.750 ReefTrack[305:60b] OBJECT TEST: <SavedTankImages:U6fRTuRo2c: (null)> {
tankImages = "<PFFile: 0x18a25890>";
tankObjectId = tsz4yvrIAN;
}
SavedTankImages: <x> is the objectId of the individual image, and tankObjectId is the tank the image is associated with. I'm getting close, but I need to know how I can effectively iterate and only grab the first item where tankObjectId matches the original objectId. Please forgive me if this sounds a little convoluted.
Thanks for the help in advance as usual.
UPDATE
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
photoHandler *cell = (photoHandler *)[collectionView dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
_tankNameArray = [_array objectAtIndex:indexPath.section * 1 + indexPath.row];
cell.tankNameLabel.text = [_tankNameArray valueForKey:#"tankName"];
cell.tankCapLabel.text = [_tankNameArray valueForKey:#"tankCapacity"];
NSArray *objectId = [_array valueForKey:#"objectId"];
for (int i = 0; i < objectId.count; i++)
{
// NSString *objectString = [[NSString alloc] init];
// objectString = [objectId objectAtIndex:i];
PFQuery *imageQuery = [PFQuery queryWithClassName:#"SavedTankImages"];
[imageQuery whereKey:#"tankObjectId" equalTo:[objectId objectAtIndex:i]];
[imageQuery getFirstObjectInBackgroundWithBlock:^(PFObject *objects, NSError *error)
{
if (!error)
{
PFFile *imageFile = [objects valueForKey:#"tankImages"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error)
{
cell.parseImage.image = [UIImage imageWithData:data];
}
}];
NSLog(#"Heres your image: %#", objects);
}
}];
}
return cell;
}
The above code selects the first available image and makes it the background for every cell in collectionView. I want to get it so that it only returns the first image for the objectId. In other words
Tank 1 = tank 1 image 1
Tank 2 = tank 2 image 1
Tank 3 = tank 3 image 1
Right now this is what it's doing:
Tank 1 = tank 1 image 1
Tank 2 = tank 1 image 1
Tank 3 = tank 1 image 1
Simple solution, sort and take just the first result. Be aware this isn't as efficient as the next solution:
// sort by createdAt or use updatedAt
[imageQuery orderByDescending:#"createdAt"];
// change from find to getFirst
//[imageQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
[imageQuery getFirstObjectInBackgroundWithBlock:^(PFObject *object, NSError *error) {
if (!error)
{
NSLog(#"OBJECT TEST: %#", object);
}
}];
A more advanced solution if you'll often be querying on this is to store a pointer to the most recent object on the tank. You can either do this in your code or create a Cloud Code method to update it automatically after each SavedTankImages object is saved (it would simply load up the related SavedTanks and set mostRecentImage to point to the saved image, then save the SavedTanks).
If you have done this, then you can just use the include: method to load the mostRecentImage with the SavedTanks.
Related
I am trying to set up three NSMutableArray to use in UITableView.
Here is my code:
for (PFObject *object in objects) {
PFUser *user = (PFUser *) object[#"user"];
[ [user objectForKey:#"image"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error)
{
//Add Comment text
[_commentsArray insertObject:[object objectForKey:#"comment"] atIndex:i];
//Add comment Id
[_commentIDArray insertObject:object.objectId atIndex:i];
//Add user image
[_picsArray insertObject:[UIImage imageWithData:data] atIndex:i ];
if (i == [objects count]-1)
{
[self.tableView reloadData];
}
}
else
{
NSLog(#"Errrror == %ld",(unsigned long)[_picsArray count]);
}
i++;
}];
}
In the PFQuery I am ordering it:
[query orderByDescending:#"createdAt"];
But as far as I can understand image in first row is large. So it takes time to download it. So it goes to second loop. Try to download image. Size is small. Download finished. Add to array. Now download for first image is finished. Add to array but to second place. How can manage it so it add items one by one in the order?
Check this:
// initially, add place holder
for (int i=0; i<objects.count; i++) {
[_commentsArray addObject:#""];
[_commentIDArray addObject:#""];
[_picsArray addObject:#""];
}
for (PFObject *object in objects) {
PFUser *user = (PFUser *) object[#"user"];
[ [user objectForKey:#"image"] getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error)
{
NSInteger orderIndex = [objects indexOfObject:object];
//Replace Comment text
[_commentsArray replaceObjectAtIndex:[object objectForKey:#"comment"] atIndex:orderIndex];
//Replace comment Id
[_commentIDArray replaceObjectAtIndex:object.objectId atIndex:orderIndex];
//Replace user image
[_picsArray replaceObjectAtIndex:[UIImage imageWithData:data] atIndex:orderIndex ];
if (i == [objects count]-1)
{
[self.tableView reloadData];
}
}
else
{
NSLog(#"Errrror == %ld",(unsigned long)[_picsArray count]);
}
i++;
}];
}
Rather than downloading image and create array to populate tableview, you have to just create array of PFObjects and use it with SDWebImage for Asynchronous image downloading without any issue or blocking UI.
I'm guessing that the question is really about not expending effort to download images beyond the scroll position while the visible rows are still being fetched.
The solution to that problem is to load images lazily, when they're needed to configure a cell in cellForRowAtIndexPath:. There's plenty of generic content available about this idea. For a parse-specific solution, see PFImageView here.
The gist is that image view will take care of loading and caching an image from the file. It will do this asynchronously, so there will be a low perceived lag. Just give the file to the image view and let it do the rest...
Your cellForRowAtIndexPath will look something like this:
// just guessing that your "objects" array is the table's datasource
PFObject *object = self.objects[indexPath.row];
PFUser *user = (PFUser *) object[#"user"];
// put a PFImageView in the cell (in this case with a tag==32)
PFImageView *imageView = (PFImageView *)[cell viewWithTag:32];
imageView.image = [UIImage imageNamed:#”placeholder.png”];
imageView.file = [user objectForKey:#"image"]; // assuming this is a file attribute
[imageView loadInBackground];
You have a problem that you try to do order based adding, where your blocks fire asynchronously so it can be in random order.
You should change to a dictionary or any other keyed data structure and use keys for your comments and pics (e.g. use comment id as the key).
Also double check if the callback of the block is executed on the main queue or any serial queue, because if it's not you need to add locks.
I had the same problem, my images were downloaded but not appearing in the order it should, my table view images and the titles were not matching.
To solve that, I created a column at my class in Parse.com that hold exclusively nameForImages, then each downloaded image is saved using this name.
The nameForImages had to be the same used for the column title, for example:
Title = Pepperoni and Four Cheese | nameForImage =
PepperoniAndFourCheese
Title - Muzzarella and Spinach | nameForImage = MuzzarellaAndSpinach
Etc...
This trick fit to solve my problem because the name of the image and the title appearing in the cell were short and had no special caracters.
I hope it helps or light a solution, good luck.
I am building an app which will query a set of images (thumbnails) from the parse server and then show them in a collection view, similar to how is done on instagram in the users profile page. I created a method which queries the data from the backend successfully :
-(void)queryForTable {
PFQuery *query = [PFQuery queryWithClassName:#"VideoApp"];
NSString * author = [[PFUser currentUser] objectForKey:#"FBName"];
[query whereKey:#"author" equalTo:author];
[query orderByAscending:#"createdAt"];
[query setCachePolicy:kPFCachePolicyNetworkOnly];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
NSLog(#"Successfully retrieved %d objects", objects.count);
[self.collectionView reloadData];
userVideosArray = [[NSMutableArray alloc] initWithCapacity:objects.count];
for (PFObject *object in objects) {
PFFile *thumbnail = [object objectForKey:#"video_thumbnail"];
[thumbnail getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if (!error) {
NSLog(#"Fetching image");
[userVideosArray addObject:[UIImage imageWithData:data]];
} else {
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
}
}
}];
}
This method successfully retrieves four objects from the back end, and is called in the ViewDidLoad method.
Then in the collection view cellForRowAtIndexPath method I try to set the queried objects images to the UIImageview on the collectionviewcell as follows:
-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
//CollectionViewcellCollectionViewCell * cell = (CollectionViewcellCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 70, 70)];
imageView.backgroundColor = [UIColor grayColor];
[cell addSubview:imageView];
PFQuery *query = [PFQuery queryWithClassName:#"VideoApp"];
NSString * info = [[PFUser currentUser] objectForKey:#"FBName"];
[query whereKey:#"author" equalTo:info];
imageView.image = [UIImage imageWithData:[userVideosArray objectAtIndex:indexPath.row]];
return cell;
}
I keep getting an NSException on imageView.image = [UIImage imageWithData:[userVideosArray objectAtIndex:indexPath.row]]; . Not 100 percent sure why. Any ideas?
You are doing this:
1) reloadData: wrong because you did not update any array before doing this. I assume that it is side effect of findObjectsInBackgroundWithBlock which is definitely wrong, because only object which performs reloadData should be responsible for updating data for datasource.
2) initializing userVideosArray with no items (userVideosArray.count == 0). Looking at your error and knowing that cellForItemAtIndexPath is invoked I'm assuming that -collectionView:numberOfItemsInSection: uses other different array to tell the number of items which is wrong, because you are trying to get item from userVideosArray which may have different number of items
3) filling userVideosArray with items in background. Keeping in mind 1 and 2 gives us an answer to your crash. in cellForItemAtIndexPath you are trying to obtain item which is still not loaded
Btw: [cell addSubview:imageView]; will keep adding image views to your cell each time collection view will reuse it
You should update collectionView after you have handled received data, not before. Your call to [self.collectionView reloadData] trickers calls to cellForItemAtIndexPath and my guess is that userVideosArray does not yet contain as many items as you're expecting.
Anyways, the crash. Try this to prevent crashing:
if (indexPath.item < userVideosArray.count)
imageView.image = [UIImage imageWithData:userVideosArray[indexPath.item]];
Btw when using collectionView, I'd recommend using item instead of row, since one collectionView row might contain several items. You know now what you're doing, and it's ok to use row, but later row vs. item terms might get confusing.
I have created a relation between two tables
LocationClass Table In this table i have a column with location images with the relation of another table ie., Assets
![This is location Class Image, If user selected a locationName, need to get the locationImages(viewRelation)][1]
Assets Table, It contains Images for each relation Now my query is how to get the data from relation database. Here if user select a location means i need to get a group of images for the relevant location ![This is my Assets Table, here need to retrieve the images based on the selected location from the locationClass Table][2]
Till now, I have done with this format
PFQuery *queryObj = [PFQuery queryWithClassName:#"LocationClass"];
sharedDelegate = (AppDelegate *)[[UIApplication sharedApplication]delegate];
// Run the query
[queryObj whereKey:#"locationImages" equalTo:[PFObject objectWithoutDataWithClassName:#"Assets" objectId:#"aAPzhdO4w6"]];
[queryObj findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error)
{
[locationArray addObjectsFromArray:objects];
sharedDelegate.locationsArray = locationArray;
[locationDropDown reloadData];
}
}];
In my location array for a single object contain
<__NSArrayM 0xaee4eb0>(
<LocationClass:ZtOP9voUak:(null)> {
LocationId = WilliamsBurgId;
LocationName = WilliamsBurg;
locationImages = "<PFRelation: 0xaf53c30>(<00000000>.(null) -> Assets)";
}
)
Solution for to pass the data from PFRelation Object
// should pass the main table name
PFQuery *query = [PFQuery queryWithClassName:#"LocationClass"];
// should pass object id for the selected row
PFObject *getImageObject = [query getObjectWithId:#"need to pass object id from the maintable"];
locationImagesArray = [[NSMutableArray alloc]init];
// To acess the data from the relation object
PFRelation *relationObj = [getImageObject relationForKey:#"locationImages"];
PFQuery *query1 = [relationObj query];
[query1 findObjectsInBackgroundWithBlock:^(NSArray *results, NSError *error) {
[locationImagesArray addObjectsFromArray:results];
for (int imgCount = 0; imgCount < [locationImagesArray count]; imgCount ++) {
PFFile *getImage1 = [[locationImagesArray valueForKey:#"Image"] objectAtIndex:imgCount];
[getImage1 getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error)
{
if (imageData!=nil) {
UIImage *image = [UIImage imageWithData:imageData];
activityImage.image = image;
NSLog(#"location image output");
NSLog(#"location image output : %#", activityImage.image);
}
}];
}
Very straight forward question here; I'm using a UICollectionView and looking to display a certain number of images per section determined by a count of the images that match the section.
For example:
Category: x
# Images in Category: y
# Images In Section: y
I've got sorting everything into the categories down, and I can get the count for each category, but I'm not sure how to return the correct value for each image in the "Category" section. This is what I've got so far:
- (NSInteger) collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
// Category Delineater Here
for (int i = 0; i <= _categoryCount.count; i++)
{
PFQuery *tiredQuery = [[PFQuery alloc] initWithClassName:#"savedImages"];
[tiredQuery whereKey:#"imageCategory" equalTo:[_categoryCount objectAtIndex:0]];
[tiredQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
{
if (!error)
{
NSLog(#"What does this return? %#", objects);
}
else
{
NSLog(#"This returns an error: %#", error);
}
}];
}
return _extractedImagesArray.count;
}
Currently, _extractedImagesArray.count is the total number of images that are downloaded. How to I change the amount returned in this section based on the number of images a particular category has?
EDIT: For Clarification of data structure:
Each image has a category affixed to it. The images are all placed in the same Class on Parse.
My ultimate goal is to get it to look similar to this ([x] representing an image in this case):
Category 1
X X X X X
Category 2
X X X
Category 3
X X X X
etc etc
This line looks like a problem:
[tiredQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error)
The method you're using runs asynchronously, so you're not going to get the results back right away. -collectionView:numberOfItemsInSection: isn't going wait around for you to find the objects asynchronously and then count them -- you need to return the number of objects immediately.
It sounds like you'll probably need to do some processing ahead of time to determine the number of objects in each section, and then remember the counts somewhere so that you can provide them when asked by the collection view.
Looks like you are using Parse.
You should not make a call to the backend while reloading data. Try something like this:
- (void)getImages {
for (int i = 0; i <= _categoryCount.count; i++) {
PFQuery *tiredQuery = [[PFQuery alloc] initWithClassName:#"savedImages"];
[tiredQuery whereKey:#"imageCategory" equalTo:[_categoryCount objectAtIndex:0]];
[tiredQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
_extractedImagesArray = objects;
[collectionView reloadData];
}
else {
NSLog(#"This returns an error: %# %#", error, [error userInfo]);
}
}];
}
}
- (NSInteger) collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return _extractedImagesArray.count;
}
Here is an example of returning different amounts of items dependent no which section you are referencing. Remember, section 0 is always the first one.
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
switch (section) {
case 0:
return xNumber;
break;
case 1:
return yNumber;
break;
case 2:
return zNumber;
}
}
I have a Parse query that runs to gather the 10 closest Arcades in your area, and I am trying to have them display those object titles in 10 separate labels. I have the following code which gathers the 10 closest and logs them, and I am trying to start by displaying the objectId in the labels but cannot figure out how to display them all and not just 1. Any suggestions?
PFQuery *query = [PFQuery queryWithClassName:#"Arcade"];
CLLocation *currentLocation = locationManager.location;
PFGeoPoint *userLocation =
[PFGeoPoint geoPointWithLatitude:currentLocation.coordinate.latitude
longitude:currentLocation.coordinate.longitude];
query.limit = 10;
[query whereKey:kPAWParseLocationKey nearGeoPoint:userLocation withinMiles:kPAWWallPostMaximumSearchDistance];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
NSLog(#"Successfully retrieved %d scores.", objects.count);
// Do something with the found objects
for (PFObject *object in objects) {
NSLog(#"%#", object.objectId);
NSString *EventTitle = object.objectId;
EventTitle1.text = EventTitle;
for (UIImageView *imageView in self.imageViews) {
__block UIImage *MyPicture = [[UIImage alloc]init];
PFFile *imageFile = [object objectForKey:#"test"];
[imageFile getDataInBackgroundWithBlock:^(NSData *data, NSError *error){
if (!error) {
MyPicture = [UIImage imageWithData:data];
imageView.image = MyPicture;
}
}];
}
for (UILabel *EventLabel in self.EventTitles){
EventLabel.text = object.objectId;
}
}
UPDATE: I have created two collection outlets, however when they display they only display the final object queried, not all 10 of them? Am I doing something wrong?
Your problem is EventTitle1.text = EventTitle;, because you explicitly reference the label. What you should be doing is updating the labels in sequence. This could be done by having the labels in an array (perhaps an IBOutletCollection) and using the iteration index. Or you could tag all of the labels and then look them up (again, using the iteration index).
But, your intended solution isn't simple and doesn't scale. It would be better to use a table view (Parse SDK even gives you an easy way to populate a table view from a query).