iOS - TableView takes time to display - ios

I have implemented a UITableView programmatically and it works fine but the only problem is that once i run the application, it just shows a white window and then after about 5 - 10 seconds, it displays the tableView. Is there a way to make it display the tableView faster?
This is what I have so far:
ACAccountStore *account = [[ACAccountStore alloc]init];
ACAccountType *accountType = [account accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
[account requestAccessToAccountsWithType:accountType options:nil completion:^(BOOL granted, NSError *error) {
if (granted == YES) {
_accountNumbers = [account accountsWithAccountType:accountType];
if ([_accountNumbers count] > 1) {
//create a nagigation bar
//create a table view
self.tableView = [self createTableView];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
[self.view addSubview: self.tableView];
}
- (UITableView *) createTableView {
CGFloat x = 0;
CGFloat y = 50;
CGFloat width = self.view.frame.size.width;
CGFloat height = self.view.frame.size.height;
CGRect tableFrame = CGRectMake(x, y, width, height);
UITableView *tableView = [[UITableView alloc]initWithFrame:tableFrame style:UITableViewStylePlain];
tableView.rowHeight = 45;
tableView.sectionFooterHeight = 22;
tableView.sectionHeaderHeight = 22;
tableView.scrollEnabled = YES;
tableView.showsVerticalScrollIndicator = YES;
tableView.userInteractionEnabled = YES;
tableView.bounces = YES;
tableView.delegate = self;
tableView.dataSource = self;
return tableView;
}
Is there a reason why it delays the tableView from being shown?

From the ACAccountStore Class Reference on requestAccessToAccountsWithType:options:completion::
The handler is called on an arbitrary queue.
However, the "Threading Considerations" section of the UIView Class Reference says:
Manipulations to your application’s user interface must occur on the main thread. Thus, you should always call the methods of the UIView class from code running in the main thread of your application. The only time this may not be strictly necessary is when creating the view object itself but all other manipulations should occur on the main thread.
Therefore, change your code to this:
if ([_accountNumbers count] > 1) {
dispatch_sync(dispatch_get_main_queue(), ^{
//create a table view
self.tableView = [self createTableView];
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
[self.view addSubview: self.tableView];
});
}

It seems that you're using a twitter account as your data source. Are you making network requests that take some time to complete, then displaying the results in the table? Your code doesn't show when the requests are made, or how cells are counted or created.

Related

Changing Collection view flow layout

I'm having this issue with switching flow layouts, in the view debugger it changes to the flow layout that I want, however in the simulator and on my phone it's a jumbled mess. I've been looking everywhere for an answer or what the problem is and I can't find a solution. ![left side is what im trying to accomplished and how the view debugger looks, right is the simulator][1]
here is the code for the collectionview and the flow layouts -
(void)setupCollectionView
{
CGRect mainscreen = [[UIScreen mainScreen]bounds];
self.originalLayout = [[UICollectionViewFlowLayout alloc]init];
self.otherLayout = [[UICollectionViewFlowLayout alloc]init];
self.collectionView = [[UICollectionView alloc]initWithFrame:mainscreen collectionViewLayout:self.originalLayout];
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"Cell"];
[self.collectionView setPagingEnabled:YES];
[self.originalLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.originalLayout setMinimumInteritemSpacing:0.0f];
[self.originalLayout setMinimumLineSpacing:0.0f];
self.originalLayout.itemSize = CGSizeMake(self.collectionView.frame.size.width, self.collectionView.frame.size.height);
[self.otherLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.otherLayout setMinimumInteritemSpacing:10.0f];
[self.otherLayout setMinimumLineSpacing:10.0f];
self.otherLayout.itemSize = CGSizeMake(self.view.frame.size.width/2, self.view.frame.size.height/2);
[self.view addSubview:self.collectionView];
}
-(void)changeLayout
{
if (self.topBar.hidden == NO) {
self.topBar.hidden = YES;
self.bottomBar.hidden = YES;
}
if (self.swapCollectionViewLayout == NO) {
self.swapCollectionViewLayout = YES;
[self.collectionView setPagingEnabled:NO];
[self.collectionView setCollectionViewLayout:self.otherLayout animated:YES];
[self.collectionView reloadData];
}
else
{
[self.collectionView setCollectionViewLayout:self.originalLayout animated:YES];
self.collectionView.pagingEnabled = YES;
self.swapCollectionViewLayout = NO;
[self.collectionView reloadData];
}
Here is a link to a photo of the issue
https://www.facebook.com/photo.php?fbid=909831825747700&l=340de9351b

Adding UICollectionViewLayout programmatically issue

I'm trying to implements this library without using storyBoard (first step for implementing this library) , because I'm creating my UIcollectionView programmatically.
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//self.view.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.collectionView];
[_collectionView registerNib:[UINib nibWithNibName:#"myCell" bundle:nil] forCellWithReuseIdentifier:#"cell3"];
[_collectionView setBackgroundColor:[UIColor colorWithRed:0.945 green:0.945 blue:0.945 alpha:1] ];
[_collectionView setTransform:CGAffineTransformMakeScale(-1, 1)];
RFQuiltLayout* layout = (id)[_collectionView collectionViewLayout];
layout.direction = UICollectionViewScrollDirectionVertical;
layout.blockPixels = CGSizeMake(100, 100);
}
- (UICollectionView *)collectionView {
if (!_collectionView) {
CGRect collectionViewFrame = self.view.bounds;
collectionViewFrame.size.height -= (self.navigationController.viewControllers.count > 1 ? 0 : (CGRectGetHeight(self.tabBarController.tabBar.bounds))) + 0;
// FMMosaicLayout *mosaicLayout = [[FMMosaicLayout alloc] init];
//// _collectionView = [[UICollectionView alloc] initWithFrame:collectionViewFrame collectionViewLayout:mosaicLayout];
// RFQuiltLayout* layout = (id)[_collectionView collectionViewLayout];
// layout.direction = UICollectionViewScrollDirectionVertical;
// layout.blockPixels = CGSizeMake(100, 100);
_collectionView = [[UICollectionView alloc] initWithFrame:collectionViewFrame collectionViewLayout:[[RFQuiltLayout alloc] init]];
_collectionView.delegate = self;
_collectionView.dataSource = self;
}
return _collectionView;
}
But this didn't worked and nothing is shown in my view (no error and empty view that's all) Also using debugger I've notified that the UICollectionView Method are never called
First of all, your collectionView method is not called because you are using _collectionView instead self.collectionView in your viewDidLoad method. You need to write self for every property to call their setter and getter methods.
Second, if you want to add custom layout without Storyboard of XIB, then you need to set it programmatically:
RFQuiltLayout* layout = [[RFQuiltLayout alloc] init];
layout.direction = UICollectionViewScrollDirectionVertical;
layout.blockPixels = CGSizeMake(100, 100);
self.collectionView.collectionViewLayout = layout;

iOS: UITableView in popover and seeing "double vision" effect

I'm working on an iPad app and at some points, I need to show a popover with options for a user to pick from. For this, I use a UITableView in a UIPopoverController. The problem is that, on an iPad (not on the simulator), when scrolling the tableview, I get a sort of "double vision" effect, where it appears like two sets of of the list exist. One that is stationary, and one that scrolls up and down.
I construct the popover like this:
self.fClassTypeList = [[NSMutableArray alloc] init];
[self.fClassTypeList removeAllObjects];
NSUInteger stringLength = 0;
(populate self.fClassTypeList, and set stringLength to the size of the longest entry)
[self setContentSizeForViewInPopover:CGSizeMake(stringLength * 15.0f, [self.fClassTypeList count] * 30)];
CGFloat tableBorderLeft = 5;
CGFloat tableBorderRight = 5;
CGRect viewFrame = self.view.frame;
viewFrame.size.width -= tableBorderLeft + tableBorderRight; // reduce the width of the table
self.fListOfItems = [[UITableView alloc] initWithFrame:viewFrame style:UITableViewStylePlain];
self.fListOfItems.delegate = self;
self.fListOfItems.dataSource = self;
[self.view addSubview:self.fListOfItems];
I put in the viewDidLayoutSubviews(…) part of the view controller, maybe I should put it somewhere else? I am not sure why this happens on the actual machine, but not the simulator.
-viewDidLayoutSubviews is a weird place to put allocations because that method can be called multiple times. So as far as your main issue goes, I believe you should move your allocations into the -init method, and move your layout code into your -viewWillAppear method.
- (id)init
{
self = [super init];
if (self)
{
self.fClassTypeList = [[NSMutableArray alloc] init];
self.fListOfItems = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
self.fListOfItems.delegate = self;
self.fListOfItems.dataSource = self;
[self.view addSubview:self.fListOfItems];
}
return self;
}
- (void )viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSUInteger stringLength = 0;
CGFloat tableBorderLeft = 5;
CGFloat tableBorderRight = 5;
CGRect viewFrame = self.view.frame;
viewFrame.size.width -= tableBorderLeft + tableBorderRight; // reduce the width of the table
self.fListOfItems.frame = viewFrame;
[self setContentSizeForViewInPopover:CGSizeMake(stringLength * 15.0f, [self.fClassTypeList count] * 30)];
}
This promotes better memory management.
As an added bonus, I would recommend you refactor the
[self setContentSizeForViewInPopover:CGSizeMake(stringLength * 15.0f, [self.fClassTypeList count] * 30)]; method into a setter method of fClassTypeList. Even better is to simply call -viewWillAppear: in that same setter method instead. This will promote good scalability as you (or someone else) continues to build upon this code later on.
It's a little confusing to see what exactly you're trying to accomplish in this code because it's so hardcoded so let me know if I'm missing the mark you're looking for (w/ an explanation why) and I'll make an edit.
Cheers

Assertion Failure in UICollectionViewData validateLayoutInRect on ios7

Assertion Failure in UICollectionViewData validateLayoutInRect on iOS7.
I am trying to delete all UICollectionView items, one by one, using a for loop; I posted my code below. I delete the UICollectionView items using deleteItemsAtIndexPaths. It's working perfectly on iOS6, but crashes in iOS7 with this exception:
Assertion Failure in UICollectionViewData validateLayoutInRect
I delete the object from collectionArray then self.collectionView, one by one, using indexPath. When I delete the 4th object its raises Assertion failure on iOS7. Here I am using performBatchUpdates.
Please help me get the proper result in iOS7. Share proper code.
Thanks in advance.
try {
for (int i=count-1; i>=0; i--) {
[self.collectionView performBatchUpdates:^(void){
[collectionArray removeObjectAtIndex:i]; // First delete the item from you model
[self.collectionView deleteItemsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]]];
} completion:nil];
[self.collectionView reloadData];
}
}
#catch (NSException *exception) {
}
#finally {
}
I actually got this crash one time not because I was returning zero for a number of sections or items in a section but because I was reusing a flow layout like this for more than 1 collection view:
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
Collection1 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 50.0f) collectionViewLayout:flowLayout];
[Collection1 setDataSource:self];
[Collection1 setDelegate:self];
[self.view addSubview:Collection1];
Collection2 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, self.view.frame.size.height) collectionViewLayout:flowLayout];
Collection2.backgroundColor = [UIColor whiteColor];
Instead if I create a new flow layout for each UICollectionView I avoid this crash. Hopefully that might help someone
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc] init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
Collection1 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, 50.0f) collectionViewLayout:flowLayout];
[Collection1 setDataSource:self];
[Collection1 setDelegate:self];
[self.view Collection1];
UICollectionViewFlowLayout *flowLayoutVert = [[UICollectionViewFlowLayout alloc] init];
[flowLayoutVert setScrollDirection:UICollectionViewScrollDirectionVertical];
Collection2 = [[UICollectionView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 320.0f, self.view.frame.size.height) collectionViewLayout:flowLayoutVert];
in iOS 10 you must disable the prefetchingEnabled:
// Swift
if #available(iOS 10, *) {
collectionView.prefetchingEnabled = false
}
//Obj C
if ([self.collectionView respondsToSelector:#selector(setPrefetchingEnabled:)]) {
self.collectionView.prefetchingEnabled = false;
}
It looks like you probably want to do this:
[self.CollectionView performBatchUpdates:^(void) {
for (int i = count - 1; i >= 0; i--) {
[collectionArray removeObjectAtIndex:i]; // First delete the item from you model
[self.CollectionView deleteItemsAtIndexPaths:#[[NSIndexPath indexPathForRow:i inSection:0]]];
}
} completion:nil];
So that all the updates are performed together. Otherwise you will end up trying to perform several lots of batch updates on top of each other.
You should implement invalidateLayout in your layoutClass and remove all kinds of UICollectionViewLayoutAttributes items in your config.
- (void)invalidateLayout{
[super invalidateLayout];
[self.itemsAttributes removeAllObjects];
}
try to call [yourCollectionView.collectionViewLayout invalidateLayout];
You should implement invalidateLayout in your layout class and remove all kinds of UICollectionViewLayoutAttributes items in your config.
- (void)invalidateLayout{
[super invalidateLayout];
[self.itemsAttributes removeAllObjects];
}
For a better way to implement invalidateLayoutWithContext, see more about UICollectionViewLayoutInvalidationContext.
From Apple Developer documentation:
When implementing a custom layout, you can override this method and use
it to process information provided by a custom invalidation context.
You are not required to provide a custom invalidation context but might
do so if you are able to provide additional properties that can help
optimize layout updates. If you override this method, you must call
super at some point in your implementation.
In my case following delegate was missing:
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView {
[collectionView.collectionViewLayout invalidateLayout];
return 1;
}

Multiple TableViews in one View - Array of data sources?

I'm trying to recreate a view controller similar to the "weather" app that comes stock on the iPhone. I've got a scrollview that contains an individual tableview per page, and I'm struggling to figure out a way to specify the data for each table view.
The tricky part is that the info in each tableview and the tableviews themselves change based on user defaults. Basically, I have an Array of dictionaries stored in user defaults, and each object in this array has its own tableview. Each dictionary contains a title, a lat and a long. When I create the tables, I also use the lat and long to get some data from the internet via an api call and parser. Here's the code:
- (void)setupScrollView
{
scrollView.delegate = self;
[self.scrollView setBackgroundColor:[UIColor clearColor]];
[scrollView setCanCancelContentTouches:NO];
scrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;
scrollView.clipsToBounds = YES;
scrollView.scrollEnabled = YES;
scrollView.pagingEnabled = YES;
scrollView.showsHorizontalScrollIndicator = NO;
//this is an array of dictionaries that hold a location title, as well as a lat and lng.
NSArray *arrayForLocations = [NSArray arrayWithArray:[appDelegate.defaults objectForKey:#"arrayOfLocationDicts"]];
NSLog(#"Array of Location Dicts holds: %#",arrayForLocations);
for (int i = 0; i < arrayForLocations.count; i++) {
CGRect frame;
frame.origin.x = self.scrollView.frame.size.width * i;
frame.origin.y = 0;
frame.size = self.scrollView.frame.size;
UIView *subview = [[UIView alloc] initWithFrame:frame];
[self.scrollView addSubview:subview];
UITableView *tbView = [[UITableView alloc]initWithFrame:CGRectMake(0, 40, scrollView.frame.size.width, scrollView.frame.size.height - 40)];
tbView.backgroundColor = [UIColor grayColor];
tbView.tag = i;
tbView.delegate = self;
tbView.dataSource = self;
[subview addSubview:tbView];
UILabel *locationLabel = [[UILabel alloc]initWithFrame:CGRectMake(5, 5, 260, 40)];
locationLabel.textColor = [UIColor blackColor];
locationLabel.text = [[arrayForLocations objectAtIndex:i]objectForKey:#"location_address"];
[subview addSubview:locationLabel];
pageControl.numberOfPages = [arrayForLocations count];
//get coordinate from dictionary
CLLocationCoordinate2D eventCoordinate;
eventCoordinate.latitude = [[[arrayForLocations objectAtIndex:i]objectForKey:#"location_latitude"]floatValue];
eventCoordinate.longitude = [[[arrayForLocations objectAtIndex:i]objectForKey:#"location_longitude"]floatValue];
//turn coordinate into data
SDJConnection *connection = [[SDJConnection alloc]init];
NSMutableArray *singleDataSource = [connection getEventInfoWithCoordinate:eventCoordinate];
//store data in array
[arrayOfDataSources addObject:singleDataSource];
NSLog(#"array of Data Sources in the scrollsetup: %#",arrayOfDataSources);
}
self.scrollView.contentSize = CGSizeMake(self.scrollView.frame.size.width * arrayForLocations.count, self.scrollView.frame.size.height);
}
So now my problem is telling each of these tables what data to display. My first thought was to set the tag of the table, as I do above, and then have something like this in the cellForRowAtIndexPath -
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
int i = tableView.tag;
if (tableView.tag = i) {
cell.textLabel.text = [[[arrayOfDataSources objectAtIndex:i]objectAtIndex:indexPath.row]eventTitle];
}
that unfortunately hasn't been working for me correctly. Does anyone have any thoughts as to how to get this done?
Thanks!!
For sure
if (tableView.tag = i) {
should be
if (tableView.tag == i) {
and even then, it's an odd construct -- not sure what you're trying to achieve there. You set the i to the tag and then you check to see if the tag is i? Doesn't really make sense -- that's where I'd suggest you take a close look and rethink your logic.

Resources