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;
}
Related
I understand that many people have asked about this on stack. However, None of these can solve my problem. I try to build a tableview programmatically and cellForRowAtIndexPath is not called. But when I drag a tableview in storyboard, everything is fine. Here is the related code.
This is the code in viewDidLoad
- (void)viewDidLoad
{
[self.navigationController.navigationBar setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[UIColor whiteColor],NSForegroundColorAttributeName,nil]];
self.navigationController.navigationBar.tintColor=[UIColor whiteColor];
self.navigationItem.title = #"发布失物招领启示";
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
[tap setCancelsTouchesInView:NO];
self.itemDetails = #"请输入详情";
self.view.translatesAutoresizingMaskIntoConstraints = NO;
self.tableView = ({
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
tableView.dataSource = self;
tableView.delegate = self;
tableView.translatesAutoresizingMaskIntoConstraints = NO;
[tableView reloadData];
tableView;
});
[self.tableView registerNib:[UINib nibWithNibName:campusCellIdentifier bundle:nil] forCellReuseIdentifier:campusCellIdentifier];
[self.tableView registerNib:[UINib nibWithNibName:imageCellIdentifier bundle:nil] forCellReuseIdentifier:imageCellIdentifier];
[self.tableView registerNib:[UINib nibWithNibName:textFieldCellIdentifier bundle:nil] forCellReuseIdentifier:textFieldCellIdentifier];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:[UIBlurEffect effectWithStyle:UIBlurEffectStyleExtraLight]];
effectView.frame = self.view.bounds;
[self.view addSubview:effectView];
self.postButton = [[AYVibrantButton alloc] initWithFrame:CGRectZero style:AYVibrantButtonStyleFill];
self.postButton.vibrancyEffect = nil;
self.postButton.text = #"发布";
self.postButton.font = [UIFont systemFontOfSize:18.0];
self.postButton.backgroundColor = [UIColor BBTAppGlobalBlue];
self.postButton.translatesAutoresizingMaskIntoConstraints = NO;
[effectView.contentView addSubview:self.postButton];
[self.view addSubview:self.tableView];
//Set up constraints
CGFloat verticalInnerSpacing = 10.0f;
CGFloat buttonHeight = 50.0f;
CGFloat tabBatHeight = self.tabBarController.tabBar.frame.size.height;
CGFloat navigationBarHeight = self.navigationController.navigationBar.frame.size.height;
[self.tableView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.view.mas_top).offset(navigationBarHeight + 20);
make.bottom.equalTo(self.view.mas_bottom).offset(-tabBatHeight - 2*verticalInnerSpacing - buttonHeight);
make.centerX.equalTo(self.view.mas_centerX);
make.width.equalTo(self.view.mas_width);
}];
[self.postButton mas_makeConstraints:^(MASConstraintMaker *make){
make.bottom.equalTo(self.view.mas_bottom).offset(-tabBatHeight - verticalInnerSpacing);
make.height.equalTo(#(buttonHeight));
make.width.equalTo(self.view.mas_width).multipliedBy(0.55);
make.centerX.equalTo(self.view.mas_centerX);
}];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 3;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3;
}
And the ones in the protocol.
Thanks for help!
Remove
self.view.translatesAutoresizingMaskIntoConstraints = NO;
From your viewDidLoad, as, in general, the system handles the setting of constraints for your view controller's view. The mechanism by which it does this is the autoresizing mask. When self.view.translatesAutoresizingMaskIntoConstraints = NO the system expects constraints to be provided for it, but no constraints have been added, I'm not sure if there's a defined behavior for this instance, but it's best to avoid it by allowing the system handle the size and position of self.view.
As others have said, if you don't add your table view as a subview of the view controller's content view then it won't try to draw itself and none of the delegate/data source methods will ever be called.
Next point: Where is the "related code" from? What method, and where in that method?
Next point:
What is with this syntax?
self.tableView = ({
UITableView *tableView = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
tableView.dataSource = self;
tableView.delegate = self;
tableView.translatesAutoresizingMaskIntoConstraints = NO;
tableView;
});
Where have you seen that syntax in use in Objective-C code? I have never seen that coding style, and I've been doing Objective-C intensively since about 2007. I gather that the braces denote a scope, and since the last thing in the scope is the expression tableView;, the whole scope evaluates to that variable? Ugh.
You Have To set datasource and delegate of UItableView to your own class.....Other wise your table view will not able to get data from your class variables.....
Follow this link to read about data source and delegation
https://developer.apple.com/library/ios/documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html
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
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;
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.
In my application I use refresh control with collection view.
UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:[UIScreen mainScreen].bounds];
collectionView.alwaysBounceVertical = YES;
...
[self.view addSubview:collectionView];
UIRefreshControl *refreshControl = [UIRefreshControl new];
[collectionView addSubview:refreshControl];
iOS7 has some nasty bug that when you pull collection view down and don't release your finger when refreshing begins, vertical contentOffset shifts for 20-30 points down which results in ugly scroll jump.
Tables have this problem too if you use them with refresh control outside of UITableViewController. But for them it could be easily solved by assigning your UIRefreshControl instance to UITableView's private property called _refreshControl:
#interface UITableView ()
- (void)_setRefreshControl:(UIRefreshControl *)refreshControl;
#end
...
UITableView *tableView = [[UITableView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.view addSubview:tableView];
UIRefreshControl *refreshControl = [UIRefreshControl new];
[tableView addSubview:refreshControl];
[tableView _setRefreshControl:refreshControl];
But UICollectionView does not have such property so there must be some way to deal with it manually.
Having the same problem and found a workaround that seems to fix it.
This seems to be happening because the UIScrollView is slowing down the tracking of the pan gesture when you pull past the edge of the scrollview. However, UIScrollView is not accounting for changes to contentInset during tracking. UIRefreshControl changes contentInset when it activates, and this change is causing the jump.
Overriding setContentInset on your UICollectionView and accounting for this case seems to help:
- (void)setContentInset:(UIEdgeInsets)contentInset {
if (self.tracking) {
CGFloat diff = contentInset.top - self.contentInset.top;
CGPoint translation = [self.panGestureRecognizer translationInView:self];
translation.y -= diff * 3.0 / 2.0;
[self.panGestureRecognizer setTranslation:translation inView:self];
}
[super setContentInset:contentInset];
}
Interestingly, UITableView accounts for this by NOT slowing down tracking until you pull PAST the refresh control. However, I don't see a way that this behavior is exposed.
- (void)viewDidLoad
{
[super viewDidLoad];
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(scrollRefresh:) forControlEvents:UIControlEventValueChanged];
[self.collection insertSubview:self.refreshControl atIndex:0];
self.refreshControl.layer.zPosition = -1;
self.collection.alwaysBounceVertical = YES;
}
- (void)scrollRefresh:(UIRefreshControl *)refreshControl
{
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:#"Refresh now"];
// ... update datasource
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:[NSString stringWithFormat:#"Updated %#", [NSDate date]]];
[self.refreshControl endRefreshing];
[self.collection reloadData];
});
}