In Swift, how to stop all the process until datas

2020-05-01 07:26发布

问题:

In CollectionView I am displaying datas from parse.com. Successfully retrieved. But unable to display in the cell. I am receiving error as Array outbound. I found out the mistake, parse is running as asynchronous. But, before parse end, collection view gets loaded. So I unable to display values in the cell. It is throwing an error. How to stop all the process until parse get loaded completely? Kindly guide me. MY CODING IS BELOW:

//VIEWCONTROLLER having collectionView

var myList : NSArray = NSArray()

let obj_par = parse_retrive()
obj_par.parse_DB() //DATAS GOING TO RETRIVE FROM PARSE

story_collection_view.reloadData() //RELOADING COLLECTIONVIEW

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        println("fifth")

        if(collectionView == date_collection_view)
        {
         :
         :
            return cell
        }

        else

        {
            var cell = collectionView.dequeueReusableCellWithReuseIdentifier("story_read", forIndexPath: indexPath) as story_reading_cell

  cell.story_title.text = myList.objectAtIndex(indexPath.row) as? String //ERROR: ARRAY OUTBOUND OF INDEX

            return cell
        }

    }

//PARSE_RETRIVE CLASS

func parse_DB() {

        println("SECOND 10")

        par_query.findObjectsInBackgroundWithBlock({(NSArray objects, NSError error) in

            if (error != nil) {
                NSLog("error " + error.localizedDescription)
            }
            else {
                println("SECOND 13")
                let sql_store_obj1 = sql_to_store() //THIRD CLASS
                self.parse_obj_arr = NSArray(array: objects)
                var j : Int = self.parse_obj_arr.count
                for (var i : Int = 0; i < j; i++) {
                    self.par_object = self.parse_obj_arr.objectAtIndex(i) as PFObject
                    self.sto = self.par_object["story_title"] as String        //STORY TITLE FROM PARSE
                    self.sto_con = self.par_object["story_content"] as String  //STORY CONTENT FROM PARSE

                    self.sto_tit.append(self.sto)        //STORING IN ARRAY VAR
                    self.sto_cont.append(self.sto_con)   //STORING IN ARRAY VAR

                } //FOR ENDING

                sql_store_obj1.sto_title_arr = self.sto_tit
                sql_store_obj1.sto_content_arr = self.sto_cont
                sql_store_obj1.parse_to_sql()

            }//ELSE ENDING

        }) //PARSE QUERY ENDING

        println("SECOND")

    } 

//THIRD CLASS

func parse_to_sql() {

        let appDel : AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        let context : NSManagedObjectContext = appDel.managedObjectContext!
        let ent = NSEntityDescription.entityForName("Sad", inManagedObjectContext: context)
        var newStory = story_sad_managed(entity: ent!, insertIntoManagedObjectContext: context)

        newStory.story_title = sto_title_arr
        newStory.story_content = sto_content_arr

        let request = NSFetchRequest(entityName: "STORY")
        request.predicate = NSPredicate(format: "story_title = %@", sto_title_arr)
        result  = context.executeFetchRequest(request, error: nil)!

        let sto_page_obj = story_page()
        sto_page_obj.myList = result //1st CLASS ARRAY 

    }

回答1:

NOTE: All code is in Objective-C, but the translation for this particular code should be trivial. If someone wants to edit my post to include the Swift code, please feel free, but please leave the Objective-C in the post.

What I do (for UICollectionView or UITableView) is create a property, normally called isLoading like this:

@property (assign, nonatomic) BOOL isLoading;

I normally initialize it like this:

- (void)viewDidLoad {
    [super viewDidLoad];
    // This could also be done in viewWillAppear/viewDidAppear
    // based on your needs/desires
    self.isLoading = NO;
    [self loadData];
}

- (void)loadData {
    if (self.isLoading == YES) {
        // Just in case a "loadData" call is made while one is pending...
        return;
    }
    // Prevent this method from firing again until 
    // data loading is done; prevent UICollectionView
    // from attempting to display missing data
    self.isLoading = YES;

    // Do whatever you need to do...

    // Clean up and prepare for UICollectionView
    self.isLoading = NO;
    [self.collectionView reloadData];
}

Now, the real secret is, of course, that you have to implement logic in your UICollectionViewDataSource methods to display data conditionally based upon self.isLoading, like this:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
    if (self.isLoading == YES) {
        // You might want to return 0 or 1, 
        // depending on whether you have a "loading"
        // placeholder cell. Assume you do:
        return 1;        
    } else {
        // Return whatever is the correct number here

    }
}

Generally, that's all I need to get my screen to delay loading the UICollectionView until the Parse query has returned correctly.

When I have multiple queries that will run concurrently, all of which should return before I consider data loading to be complete, then I create a BOOL property for each of them, setting the flags as appropriate, and I funnel all query returns through a checkIfDone method. Something like this:

@property (assign, nonatomic) BOOL data1Loaded;
@property (assign, nonatomic) BOOL data2Loaded;
@property (assign, nonatomic) BOOL data3Loaded;
@property (assign, nonatomic) BOOL data4Loaded;

- (void)loadData {
    if (self.isLoading == YES) {
        return;
    }
    self.isLoading   = YES;
    self.data1Loaded = NO;
    self.data2Loaded = NO;
    self.data3Loaded = NO;
    self.data4Loaded = NO;

    // Call each of the separate data loading methods...
    [self loadData1];
    [self loadData2];
    [self loadData3];
    [self loadData4];

    // Notice that I don't do any reloadData calls here...

}

- (void)loadData1 {
    PFQuery *query = // Some query creation...
    [query findObjectsInBackgroundWithBlock:
        ^(NSArray *objects, NSError *error) {
            if (error != nil) { 
                // Handle "got error"

            } else {
                // Handle "got good data"

            }
            // Either way, #1 is done, so:
            self.data1Loaded = YES;
            // This pattern checks for completion and
            // runs the completion code only when all
            // 4 (in this case) items/queries have returned
            [self checkIfDone];
        }
     ];
}

- (void)checkIfDone {
    if (self.data1Loaded == YES && 
        self.data2Loaded == YES && 
        self.data3Loaded == YES && 
        self.data4Loaded == YES) 
    {
        // Clean up and prepare for UICollectionView
        self.isLoading = NO;
        [self.collectionView reloadData];
    }
}

Caveat: This assumes that any subsequent calls to loadData will occur in the background and that any subsequent calls to [collectionView reloadData] will only take place at the end of your query calls. If anything else might call reloadData in the meantime, you will need more advanced logic to ensure that the correct data is loaded correctly.


Side Note: When doing something like this, don't forget to show the user an indication that work is progressing. I like to use the open source MBProgressHUD. It's available here on GitHub. I have found it invaluable for doing exactly what you're talking about doing.