-1

I'm using Parse as my backend. Saving/fetching objects runs in a separate thread from the main thread, so if you are doing anything with the data you are trying to fetch, you must do it in a background block, like this:

[Card fetchInBackgroundWithBlock:^(PFObject *object, NSError *error) {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    ...
    //setting up button based on Card's name
    ...
    [buttonArray addObject:button];
}];

This creates a new thread, and when the object has been fetched from Parse, the code inside the block runs.

I'm adding a button for each Card object from an array of cards attached to the parse user. When you press the card button, you can see some info about it, and also choose to delete it. I perform an unwind segue which handles removing the button for the deleted card as well as shifting any buttons beneath the deleted one up, so there aren't gaps. My problem is that since the buttons are created and added to my array inside of the other thread, some objects manage to be fetched out of order. So my screen may show buttons in the order 1 2 3 4, but my array holds buttons [3,2,1,4]. How can I make sure I am keeping the buttons in the array in the same order that they appear on screen, which is the same order the cards are stored in my Parse array?

Here's how I'm deleting buttons currently, which is moving incorrect buttons:

-(IBAction)prepareForUnwindPaymentCardDeleted:(UIStoryboardSegue *)segue
{
    [buttonArray removeObjectAtIndex:cardNum];
    for( int i = cardNum; i < [buttonArray count]; i++)
    {
        UIButton *button = [buttonArray objectAtIndex:i];
        [button setTag:i];
        [button setFrame:CGRectMake(xCenter-width/2, button.frame.origin.y - 52, width, height )];
    }
    yValue -= 52;
}

yValue is the y coordinate of the button's origin, and corresponds to the y coordinate that the next button should be placed at if I add a new card/button. Hence, when I remove a button/card, I decrement yValue so I don't leave a gap between my current last button and a new button. cardNum is a variable that is equal to the index of the card in my Parse array, and is also the index of the button in the order that it appears. However, I've found that it isn't always the index of the button inside of buttonArray.

What I want is for my NSMutableArray buttonArray to have its indices correspond to the order the buttons appear on the screen, so if I delete button 3, I have to shift buttons 4-n up to fill in the gap, then decrement each of their tags to correspond to the correct card.

edit - My solution ended up being to just create the button and add it to my array before the fetchinbackground call. That way, everything was being added in the order I expected. I improperly used the insertObjectAtIndex method, but since I was adding to the index equal to count, it didn't throw any errors. When I learn a little more about them, I'll be using an NSMutableDictionary instead, as suggested by Josh and Duncan.

Jake T.
  • 4,308
  • 2
  • 20
  • 48
  • The Parse tag is unnecessary and not inherently related. I've add the asynchronous tag which is more appropriate. – nhgrif Mar 04 '15 at 21:11
  • 1
    1) Actually read the spec for NSMutableArray. 2) Learn about synchronization in Objective-C. – Hot Licks Mar 04 '15 at 21:13
  • possible duplicate of [Adding an object at a specific index of an NSMutableArray](http://stackoverflow.com/questions/5137359/adding-an-object-at-a-specific-index-of-an-nsmutablearray) – nhgrif Mar 04 '15 at 21:14
  • 1
    I seriously doubt if arrayWithCapacity will let you add objects past the end of an array. The method arrayWithCapacity doesn't fill the array, it just allocates enough internal storage for the predicted number of elements. in fact, the capacity is really a hint to the class about how to manage internal storage. – Duncan C Mar 05 '15 at 00:15

3 Answers3

2

Change your design, and don't use an array for the buttons. You're trying to hack your way around a solved problem. You have one set of objects, the buttons, each of which needs to be associated with one of another set of objects, the cards. This is a mapping, so use a mapping collection: an NSMutableDictionary.

Alternatively, but less desirable because it's mixing up MVC separation, add a button property to your Card type and have each card hold a (ideally weak) pointer to its associated button.

jscs
  • 63,694
  • 13
  • 151
  • 195
  • The Card is a Parse Object, which can only hold simpler types. I can't add the buttons to the cards. I'll look into the NSMutableDictionary right now, though. – Jake T. Mar 04 '15 at 21:50
2

NSArrays can't have empty places in them, so you can't use insertObject:atIndex: to insert elements past the end of the array.

I believe you CAN use it to add an element at the end:

NSMutableArray *array = [NSMutableArray arrayWithCapcity: 10];
int index = 0;

[array insertObject @"foo" atIndex: index++];

or even

array[index++] = @"foo";

As Josh pointed out, if you want to map from buttons to objects, use a dictionary.

If you really want a "sparse" array then you can fill the empty spaces with NSNull objects and then use replaceObjectAtIndex:withObject:.

Something like this:

int count = 10;
NSMutableArray *array = [NSMutableArray arrayWithCapcity: count];
int index = 0;
for (int index = 0; index<count; index++);
   array[index] = [NSNull null];

and then to replace an object (e.g. at index 7):

[array replaceObjectAtIndex: 7 withObject @"foo"];

or

array[7] = @"foo";

NSNull objects exist for this specific purpose. They are essentially a "the page intentionally left blank" object for collections like arrays. You can then use test code like

if (array[7] == [NSNull null)
  array[7] = @"foo";

...to make decisions based on "empty" places in your array. (In this rare case you can use == to compare objects for equality, since there is only one NSNull object. It's a singleton.)

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • I had taken the lines of code initializing the button and adding it to the array out of the fetchInBackground block, so it turns out I was adding them in the proper order and always adding them to the index equal to the count, which is adding it to the end of the array like you said I can do. Just taking the lines out of the background code was the simple fix I was looking for. Would you recommend using a dictionary instead, still? I should never have more than 3 buttons, and often it'll only be one. Is using an array over a map something that'd get the app turned down from the app store? – Jake T. Mar 05 '15 at 21:33
  • Apple doesn't give a rat's patootie what data structures you use internally in your program. They will reject you for using private APIs, or violating their human interface guidelines, crashing, displaying inappropriate content, etc. – Duncan C Mar 06 '15 at 00:57
  • I don't quite understand what you're trying to do so it's a little hard to recommend a solution. It does look like you have a problem with manipulating the same data structure from 2 different threads, which is a total no-no. You need to design your code so that the user can't add or delete buttons while the server is updating the content from the background. – Duncan C Mar 06 '15 at 00:59
  • Also, you can't manipulate UI elements from the background. Your code that is creating buttons is probably even a problem (I'd have to do some research to tell if creating buttons is ok from a background thread. Adding them to your display or manipulating them once they are on the screen from a background thread is **certainly** not allowed – Duncan C Mar 06 '15 at 01:00
-1

Have you tried insertObject:atIndex: method in NSMutableArray? You can use indexOfObject: to get the corresponding index for a card in your card array.

Peng90
  • 300
  • 2
  • 9
  • I was looking for an addObject:atIndex: function and overlooked this one! Thanks. I also needed to use [NSMutableArray arrayWithSize:numCards], or insertObject:atIndex throws an error. Additionally, removing the button from my array didn't delete the button from the view, so sometimes when the buttons were shifted, one would slide under a 'deleted' one, making it look like I deleted the wrong one. For now, I'm setting it to hidden before removing it from the array, so it appears deleted, until I find a more proper solution. – Jake T. Mar 04 '15 at 22:28
  • @JakeT. - How your code is working, even with the `[NSMutableArray arrayWithSize:numCards]` is unknown. Just read the description of `insertObject:atIndex:` and you'll see it clearly states *"you cannot insert an object at an index greater than the current count of an array"*, also see Duncan C's answer. Don't solve your problem this way, it *will* go wrong. See the answers from Duncan & Josh for ways to do it correctly. – CRD Mar 05 '15 at 02:05
  • @JakeT. I have to say my answer is not as detailed and thorough than answers from Duncan & Josh. Please be mindful when dealing with mutable arrays and use `addObject`, `replaceObjectAtIndex:withObject` and `insertObject:atIndex:` conditionally. – Peng90 Mar 05 '15 at 03:59
  • @CRD While trying to fix the error, I created a function that sets up my buttons for each card. The function handles the parse fetching (where the asynchronous code runs), and right before the function calls I initialize the button and add it to the array. I guess since I'm adding them in order to the index that is equal to the count of the array, it works fine. I didn't think that this was a solution to my problem, as I still had the buttons being behind one another making it look like I deleted the wrong button. – Jake T. Mar 05 '15 at 21:21