I am trying to hide and display section headers when there is or isn't data populating the tableview. The code I have now works as intended occasionally, but mostly, one or both of the section headers will remain displayed, (However, if I drag the header off screen when there is no data it will disappear as intended).
This is the code I am using to hide/unhide the sections.
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Create custom header titles for desired header spacing
if (section == 0) {
UIView *headerView;
if (self.searchUsers.count || self.searchTracks.count)
{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
}
else{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
}
HeaderTitleLabel *headerTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 10, 150, 20) headerText:#"USERS"];
[headerView addSubview:headerTitle];
return headerView;
}
else if (section == 1){
UIView *headerView;
if (self.searchUsers.count || self.searchTracks.count)
{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
}
else{
headerView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 0, 0)];
}
HeaderTitleLabel *headerTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 0, 150, 20) headerText:#"SONGS"];
[headerView addSubview:headerTitle];
return headerView;
}
else
return nil;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
//custom header spacing
if (section == 0) {
if (self.searchUsers.count || self.searchTracks.count)
{
return 40;
}
else
return 0;
}
else if (section == 1) {
if (self.searchUsers.count || self.searchTracks.count)
{
return 50;
}
else
return 0;
}
else
return 0;
}
I check if my array has objects, if not then set the frame's height to 0. This doesn't seem to be working though. Any ideas how I should go about doing this? Thank you.
Your model should drive your table view. Don't just return a 'number of rows' value from your model, but also a 'number of sections' value.
Consider an array that contains two arrays as your model.
You are using wrong approach of table view. Can you clarify what do you exactly want to do. I could help you. Rather then UIView use UITableView Cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.searchUsers.count || self.searchTracks.count)
{
if (indexPath.section ==0) {
static NSString *simpleTableIdentifier = #"CategoryCell";
CategoryCell *cell = (CategoryCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CategoryCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.lbl_CatName.text = [[self.CategoryArray objectAtIndex:indexPath.row] objectForKey:kNAME];
return cell;}
else if (indexPath.section ==1){
static NSString *EarnPointIdentifier = #"EarnPointTableCell";
EarnPointTableCell *EarnPointcell = (EarnPointTableCell *)[tableView dequeueReusableCellWithIdentifier:EarnPointIdentifier];
if (EarnPointcell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"EarnPointTableCell" owner:self options:nil];
EarnPointcell = [nib objectAtIndex:0];
}
return EarnPointcell ;
}
else{
//it will have transparent background and every thing will be transparent
static NSString *RewardIdentifier = #"RewardTableCell";
RewardTableCell *RewardCell = (RewardTableCell *)[tableView dequeueReusableCellWithIdentifier:RewardIdentifier];
if (RewardCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"RewardTableCell" owner:self options:nil];
RewardCell = [nib objectAtIndex:0];
}
return RewardCell ;
}
}
}
It seems to be working fine now. What I did was create boolean values to keep track of my data when my data source is nil. Like so;
[SoundCloudAPI getTracksWithSearch:userInput userID:nil withCompletion:^(NSMutableArray *tracks) {
self.searchTracks = tracks;
[self.tableView beginUpdates];
if (tracks.count) {
self.songsHeaderIsHidden = NO;
}
else
self.songsHeaderIsHidden = YES;
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
[[self.tableView headerViewForSection:1] setNeedsLayout];
[[self.tableView headerViewForSection:1] setNeedsDisplay];
[self.tableView endUpdates];
}];
Then set the header accordingly..
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
//Create custom header titles for desired header spacing
if (section == 0) {
if (self.userHeaderIsHidden) {
return nil;
}
else {
UIView *userHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
HeaderTitleLabel *usersHeaderTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 10, 150, 20) headerText:#"USERS"];
[userHeaderView addSubview:usersHeaderTitle];
return userHeaderView;
}
}
else if (section == 1){
if (self.songsHeaderIsHidden) {
return nil;
}
else {
UIView *songsHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
songsHeaderView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 40)];
HeaderTitleLabel *songsHeaderTitle = [[HeaderTitleLabel alloc] initWithFrame:CGRectMake(17, 0, 150, 20) headerText:#"SONGS"];
[songsHeaderView addSubview:songsHeaderTitle];
return songsHeaderView;
}
}
else
return nil;
}
Personally, I would use UICollectionView and write your own layout. That way you can explicitly layout where the section headers will be at all times. The best approach is to probably subclass UICollectionViewFlowLayout because you get all the superclass properties for free (such as itemSize, headerReferenceSize, etc.)
Here's an implementation I wrote for having sticky headers, like the Instagram app.
Notice that in prepareLayout, I just calculate the normal positions of every element. Then in layoutAttributesForElementInRect I go through and figure out where to place the header. This is where you can go through and see if the header should be shown or not.
import UIKit
class BoardLayout: UICollectionViewFlowLayout {
private var sectionFrames: [CGRect] = []
private var headerFrames: [CGRect] = []
private var footerFrames: [CGRect] = []
private var layoutAttributes: [UICollectionViewLayoutAttributes] = []
private var contentSize: CGSize = CGSizeZero
override func prepareLayout() {
super.prepareLayout()
self.sectionFrames.removeAll(keepCapacity: false)
self.headerFrames.removeAll(keepCapacity: false)
self.footerFrames.removeAll(keepCapacity: false)
self.layoutAttributes.removeAll(keepCapacity: false)
let sections = self.collectionView?.numberOfSections()
var yOffset: CGFloat = 0.0
for var i: Int = 0; i < sections; i++ {
let indexPath = NSIndexPath(forItem: 0, inSection: i)
var itemSize: CGSize = self.itemSize
if let d = self.collectionView?.delegate as? UICollectionViewDelegateFlowLayout {
if let collection = self.collectionView {
if let size = d.collectionView?(collection, layout: self, sizeForItemAtIndexPath: NSIndexPath(forItem: 0, inSection: i)) {
itemSize = size
}
}
}
let headerFrame = CGRect(x: 0, y: yOffset, width: self.headerReferenceSize.width, height: self.headerReferenceSize.height)
self.headerFrames.append(headerFrame)
var headerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withIndexPath: indexPath)
headerAttribute.frame = headerFrame
headerAttribute.zIndex = 1000
self.layoutAttributes.append(headerAttribute)
yOffset += self.headerReferenceSize.height - 1 // - 1 so that the bottom border of the header and top border of the cell line up
let sectionFrame = CGRect(x: 0, y: yOffset, width: itemSize.width, height: itemSize.height)
self.sectionFrames.append(sectionFrame)
var sectionAttribute = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath)
sectionAttribute.frame = sectionFrame
self.layoutAttributes.append(sectionAttribute)
yOffset += itemSize.height
let footerFrame = CGRect(x: 0, y: yOffset, width: self.footerReferenceSize.width, height: self.footerReferenceSize.height)
self.footerFrames.append(footerFrame)
var footerAttribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withIndexPath: indexPath)
footerAttribute.frame = footerFrame
self.layoutAttributes.append(footerAttribute)
yOffset += self.minimumLineSpacing + self.footerReferenceSize.height
}
self.contentSize = CGSize(width: self.collectionView!.width, height: yOffset)
}
override func layoutAttributesForElementsInRect(rect: CGRect) -> [AnyObject]? {
var newAttributes: [UICollectionViewLayoutAttributes] = []
for attributes in self.layoutAttributes {
let frame = attributes.frame
if !CGRectIntersectsRect(frame, CGRect(x: 0, y: rect.origin.y, width: rect.size.width, height: rect.size.height)) {
continue
}
let indexPath = attributes.indexPath
let section = indexPath.section
if let kind = attributes.representedElementKind {
if kind == UICollectionElementKindSectionHeader {
var headerFrame = attributes.frame
let footerFrame = self.footerFrames[section]
if CGRectGetMinY(headerFrame) <= self.collectionView!.contentOffset.y {
headerFrame.origin.y = self.collectionView!.contentOffset.y
}
if CGRectGetMinY(headerFrame) >= CGRectGetMinY(footerFrame) {
headerFrame.origin.y = footerFrame.origin.y
}
attributes.frame = headerFrame
self.headerFrames[section] = headerFrame
} else if kind == UICollectionElementKindSectionFooter {
attributes.frame = self.footerFrames[section]
}
} else {
attributes.frame = self.sectionFrames[section]
}
newAttributes.append(attributes)
}
return newAttributes
}
override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let frame = self.sectionFrames[indexPath.item]
let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)
attributes.frame = frame
return attributes
}
override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes! {
let attributes = super.layoutAttributesForSupplementaryViewOfKind(elementKind, atIndexPath: indexPath)
if elementKind == UICollectionElementKindSectionHeader {
attributes.frame = self.headerFrames[indexPath.section]
} else if elementKind == UICollectionElementKindSectionFooter {
attributes.frame = self.footerFrames[indexPath.section]
}
return attributes
}
override func collectionViewContentSize() -> CGSize {
return self.contentSize
}
override func shouldInvalidateLayoutForBoundsChange(newBounds: CGRect) -> Bool {
return true
}
}
I'd recommend using the UICollectionViewDelegateFlowLayout methods so you can dynamically determine the reference size of the header.
Related
I want to implement this type of UI (Horizontal Scrolling).
I know its kind of "iCarousel", Type = iCarouselTypeRotary. I tried with this library but I am getting this type UI. I cant able to customize fully:
If anyone know how to do this UI in native way orelse any library, please let me know. Any inputs will be appreciated.
The iCarousel library provides the iCarouselTypeCustom type.
The following is a custom example.
override func awakeFromNib() {
super.awakeFromNib()
for i in 0 ... 6 {
items.append(i)
}
}
override func viewDidLoad() {
super.viewDidLoad()
carousel.type = .custom
}
func numberOfItems(in carousel: iCarousel) -> Int {
return items.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: UIView?) -> UIView {
var label: UILabel
var itemView: UIImageView
if let view = view as? UIImageView {
itemView = view
label = itemView.viewWithTag(1) as! UILabel
} else {
itemView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
itemView.image = UIImage(named: "page.png")
itemView.layer.cornerRadius = 5
itemView.contentMode = .center
label = UILabel(frame: itemView.bounds)
label.backgroundColor = .clear
label.textAlignment = .center
label.font = label.font.withSize(50)
label.tag = 1
itemView.addSubview(label)
}
label.text = "\(items[index])"
return itemView
}
func carousel(_ carousel: iCarousel, valueFor option: iCarouselOption, withDefault value: CGFloat) -> CGFloat {
switch option {
case .visibleItems:
let elemento:Int = self.carousel.currentItemIndex as Int
print(elemento)
if elemento == 0 {
return 2;
}else if elemento == items.count-1 {
return 2;
}else {
return 3;
}
default:
return value
}
}
func carousel(_ carousel: iCarousel, itemTransformForOffset offset: CGFloat, baseTransform transform: CATransform3D) -> CATransform3D {
let offsetFactor:CGFloat = self.carousel(carousel, valueFor: .spacing, withDefault: 1.25) * carousel.itemWidth
let zFactor: CGFloat = 400.0
let normalFactor: CGFloat = 0
let shrinkFactor: CGFloat = 100.0
let f: CGFloat = sqrt(offset * offset + 1)-1
var trans = transform
trans = CATransform3DTranslate(trans, offset * offsetFactor, f * normalFactor, f * (-zFactor))
trans = CATransform3DScale(trans, 1 / (f / shrinkFactor + 1.0), 1 / (f / shrinkFactor + 1.0), 1.0 )
return trans
}
func carousel(_ carousel: iCarousel, shouldSelectItemAt index: Int) -> Bool {
return true
}
Finally I did with custom view and I have achieved UI as I need it. Here, I am providing my code for someone needy.
- (void)viewDidLoad
{
[super viewDidLoad];
_iCarouselItems = [[NSMutableArray alloc]init];
for (int i = 0; i < 10; i++)
{
[_iCarouselItems addObject:#(i)];
}
self.iCarouselView.dataSource = self;
self.iCarouselView.delegate = self;
_iCarouselView.type = iCarouselTypeCustom;
dispatch_async(dispatch_get_main_queue(), ^{
[_iCarouselView reloadData];
});
}
#pragma mark iCarousel methods
- (NSInteger)numberOfItemsInCarousel:(iCarousel *)carousel
{
return [_iCarouselItems count];
}
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSInteger)index reusingView:(UIView *)view
{
UILabel *label = nil;
//create new view if no view is available for recycling
if (view == nil)
{
NSLog(#"viewForItemAtIndex is %ld",(long)index);
//don’t do anything specific to the index within
//this `if (view == nil) {…}` statement because the view will be
//recycled and used with other index values later
view = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200.0f)];
// ((UIImageView *)view).image = [UIImage imageNamed:#"smiley-400x400.jpg"];
view.contentMode = UIViewContentModeCenter;
view.backgroundColor = [UIColor whiteColor];
view.layer.cornerRadius = 8;
view.layer.masksToBounds = true;
view.layer.borderColor = [UIColor grayColor].CGColor;
view.layer.borderWidth = 2;
label = [[UILabel alloc] initWithFrame:view.bounds];
label.backgroundColor = [UIColor clearColor];
label.textAlignment = NSTextAlignmentCenter;
label.font = [label.font fontWithSize:50];
label.tag = 1;
[view addSubview:label];
}
else
{
//get a reference to the label in the recycled view
label = (UILabel *)[view viewWithTag:1];
}
label.text = [_iCarouselItems[index] stringValue];
return view;
}
- (CATransform3D)carousel:(iCarousel *)carousel itemTransformForOffset:(CGFloat)offset baseTransform:(CATransform3D)transform
{
CGFloat offsetFactor = [self carousel:carousel valueForOption:iCarouselOptionSpacing withDefault:0.3]*carousel.itemWidth;
CGFloat zFactor = 400.0;
CGFloat normalFactor = 0;
CGFloat shrinkFactor = 100.0;
CGFloat f = sqrt(offset * offset + 1)-1;
transform = CATransform3DTranslate(transform, offset * offsetFactor, f * normalFactor, f * (-zFactor));
transform = CATransform3DScale(transform, 1 / (f / shrinkFactor + 1.0), 1 / (f / shrinkFactor + 1.0), 1.0 );
return transform;
}
- (BOOL)carousel:(iCarousel *)carousel shouldSelectItemAtIndex:(NSInteger)index
{
return true;
}
- (void)carousel:(iCarousel *)carousel didSelectItemAtIndex:(NSInteger)index
{
NSLog(#"Selected carouselindex is %ld",(long)index);
}
- (NSInteger)numberOfPlaceholdersInCarousel:(iCarousel *)carousel
{
//note: placeholder views are only displayed on some carousels if wrapping is disabled
return 0;
}
There are several solutions available for 3D carousels.
Take a look at that one for example:https://www.cocoacontrols.com/controls/parallaxcarousel
You can find the source code on Github. This project uses CABasicAnimation und you can install it using CocaPods.
I use UICollectionView and refer to this link implement scroll table view Fixed single column and row like below image.
but when I want to add shadow property, it display messy.
I think the reason is that I reuse cell when render visual view, but I don't know how to fix it :(
provide my code below, thanks for your time!
import Foundation
import SwiftyJSON
#objc(RNCollection)
class RNCollection : UICollectionView, UICollectionViewDataSource, UICollectionViewDelegate {
var contentCellIdentifier = "CellIdentifier"
var collectionView: UICollectionView!
static var dataSource = []
static var sections = 0
static var rows = 0
override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: CGRectZero, collectionViewLayout: RNCollectionLayout())
self.registerClass(RNCollectionCell.self, forCellWithReuseIdentifier: contentCellIdentifier)
self.directionalLockEnabled = false
self.backgroundColor = UIColor.whiteColor()
self.delegate = self
self.dataSource = self
self.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setConfig(config: String!) {
var json: JSON = nil;
if let data = config.dataUsingEncoding(NSUTF8StringEncoding) {
json = JSON(data: data);
};
RNCollection.dataSource = json["dataSource"].arrayObject!
RNCollection.sections = json["sections"].intValue
RNCollection.rows = json["rows"].intValue
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let contentCell: RNCollectionCell = collectionView.dequeueReusableCellWithReuseIdentifier(contentCellIdentifier, forIndexPath: indexPath) as! RNCollectionCell
contentCell.textLabel.textColor = UIColor.blackColor()
if indexPath.section == 0 {
contentCell.backgroundColor = UIColor(red: 232/255.0, green: 232/255.0, blue: 232/255.0, alpha: 1.0)
if indexPath.row == 0 {
contentCell.textLabel.font = UIFont.systemFontOfSize(16)
contentCell.textLabel.text = (RNCollection.dataSource[0] as! NSArray)[0] as? String
} else {
contentCell.textLabel.font = UIFont.systemFontOfSize(14)
contentCell.textLabel.text = (RNCollection.dataSource[0] as! NSArray)[indexPath.row] as? String
}
} else {
contentCell.textLabel.font = UIFont.systemFontOfSize(12)
contentCell.backgroundColor = UIColor.whiteColor()
if(indexPath.section % 2 == 0) {
contentCell.backgroundColor = UIColor(red: 234/255.0, green: 234/255.0, blue: 236/255.0, alpha: 1.0)
}
if indexPath.row == 0 {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[0] as? String
contentCell.layer.shadowOffset = CGSize(width: 3, height: 3)
contentCell.layer.shadowOpacity = 0.7
contentCell.layer.shadowRadius = 2
} else {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[indexPath.row] as? String
}
}
return contentCell
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return RNCollection.rows
}
// MARK - UICollectionViewDataSource
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return RNCollection.sections
}
}
You are specifying your customizations for a certain section/row and in your else condition you just set the text. You should specify your required/default customization in your else condition. Consider this part of your code
if indexPath.row == 0 {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[0] as? String
contentCell.layer.shadowOffset = CGSize(width: 3, height: 3)
contentCell.layer.shadowOpacity = 0.7
contentCell.layer.shadowRadius = 2
} else {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[indexPath.row] as? String
}
You have specified your shadowOffset,shadowOpacity and shadowRadius in your if condition, but you ignored them in your else condition. If you specify the appearance in the else condition you won't have the problem you are facing now. It should look something like this
if indexPath.row == 0 {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[0] as? String
contentCell.layer.shadowOffset = CGSize(width: 3, height: 3)
contentCell.layer.shadowOpacity = 0.7
contentCell.layer.shadowRadius = 2
} else {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[indexPath.row] as? String
contentCell.layer.shadowOffset = 0
contentCell.layer.shadowOpacity = 0
contentCell.layer.shadowRadius = 0
}
If you reset everything properly in all your condition checks, this problem won't occur.
You set up shadow when your row is equal to 0:
if indexPath.row == 0 {...}
but if the row is not equal to zero collection view can reuse the cell when the shadow was already set. The solution is reset the shadow when row is not 0, in your code you can do something like this (see todo):
if indexPath.row == 0 { // Set up shadow here
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[0] as? String
contentCell.layer.shadowOffset = CGSize(width: 3, height: 3)
contentCell.layer.shadowOpacity = 0.7
contentCell.layer.shadowRadius = 2
} else {
contentCell.textLabel.text = (RNCollection.dataSource[indexPath.section] as! NSArray)[indexPath.row] as? String
//TODO: Remove the shadow here
}
If you wanna save the cell setting everytime when your cell appears,you should set every possible condition of the method "func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell".Do not prevent the reuse of collectionView only if your data or items is few.
In Objective-C, but in Swift, the logic should be the same and not that hard to translate.
In RNCollectionCell.
//These enums may have a better nam, I agree
typedef enum : NSUInteger {
RNCollectionCellStyleColumnTitle,
RNCollectionCellStyleColumnValue,
RNCollectionCellStyleRowTitle,
RNCollectionCellStyleUpperLeftCorner,
} RNCollectionCellStyle;
-(void)titleIt:(NSString *)title forStyle:(RNCollectionCellStyle)style forSection:(NSUInteger)section
{
self.textLabel.textColor = [UIColor blackColor]; //Since it's always the same could be done elsewhere once for all.
//Note I didn't check completely if the case were good, and I didn't add the background color logic, but it should be done here too.
switch (style)
{
case RNCollectionCellStyleColumnTitle:
{
self.textLabel.font = [UIFont systemFontOfSize:12];
self.layer.shadowOffset = CGSizeMake(3, 3);
self.layer.shadowOpacity = 0.7;
self.layer.shadowRadius = 2;
}
break;
case RNCollectionCellStyleColumnValue:
{
self.textLabel.font = [UIFont systemFontOfSize:12];
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowOpacity = 0;
self.layer.shadowRadius = 0;
}
break;
case RNCollectionCellStyleRowTitle:
{
self.textLabel.font = [UIFont systemFontOfSize:14];
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowOpacity = 0;
self.layer.shadowRadius = 0;
}
break;
case RNCollectionCellStyleUpperLeftCorner:
{
self.textLabel.font = [UIFont systemFontOfSize:16];
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowOpacity = 0;
self.layer.shadowRadius = 0;
}
break;
default:
break;
}
self.textLabel.text = title;
}
-(void)prepareForReuse
{
[super prepareForReuse];
self.layer.shadowOffset = CGSizeZero;
self.layer.shadowOpacity = 0;
self.layer.shadowRadius = 0;
}
In RNCollection:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
RNCollectionCell *contentCell = [collectionView dequeueReusableCellWithReuseIdentifier: contentCellIdentifier forIndexPath:indexPath];
//Here check what style you need to do
//Also, you seems to always do dataSource[indexPath.section][indexPath.row], you just use the "if test values".
[contentCell titleIt:dataSource[indexPath.section][indexPath.row]
forStyle:RNCollectionCellStyleColumnTitle
forSection:[indexPath section]];
return contentCell;
}
As said, I didn't check if all the cases were correct according to your needs.
But, you don't "prevent reuse of cell", it's optimized.
You need to "reset" values before reused or when you set the values. In theory the thing done in prepareForReuse should be enough and you shouldn't have to "reset" the layer effect in the switch, but I honestly didn't check it.
My UIViewController splits to 2:
UIView
UITableView
I've added a footer view to table in order to hide the rest of the table.
Since I cannot use static cells and also hide all bottom view of the table, I did it a bit tricky.
But the table view didn't scroll to top when I tap on my textfields correctly.
The keyboard hides the UITextField and doesn't scroll to correct point.
How can I fix it?
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 6;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier;
if (indexPath.row == 0) identifier = #"cell_name";
else if (indexPath.row == 1) identifier = #"cell_password";
else if (indexPath.row == 2) identifier = #"cell_password_verify";
else if (indexPath.row == 3) identifier = #"cell_email";
else if (indexPath.row == 4) identifier = #"cell_cellphone";
else if (indexPath.row == 5) identifier = #"cell_social";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
return cell;
}
- (void)configureUI
{
UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), 1)];
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
self.edgesForExtendedLayout = UIRectEdgeNone;
self.extendedLayoutIncludesOpaqueBars = NO;
self.automaticallyAdjustsScrollViewInsets = NO;
}
UPDATE:
Problem was, scroll view cannot scroll because tableFooterView is too short, then I modified my code.
Basically,#Roger Nolan right, I also added the following code and now it works perfect:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *)textField.superview.superview.superview;
[self.tableView scrollToRowAtIndexPath:[self.tableView indexPathForCell:cell] atScrollPosition:UITableViewScrollPositionTop animated:YES];
}
- (void)registerObservers
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidHide:)
name:UIKeyboardDidHideNotification
object:nil];
}
- (void)keyboardDidShow:(NSNotification *)notification
{
CGFloat keyboardHeight = [CoreGraphicsHandler keyboardFramFromNotification:notification].size.height;
UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), keyboardHeight)];
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
}
- (void)keyboardDidHide:(NSNotification *)notification
{
UIView *tableFooterView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth([UIScreen mainScreen].bounds), 1)];
tableFooterView.backgroundColor = [UIColor whiteColor];
self.tableView.tableFooterView = tableFooterView;
}
You need to make the controller a delegate of your text fields and then send the tableview scrollToRowAtIndexPath:atScrollPosition: animated:
See this exact dupe: Making a UITableView scroll when text field is selected
I had a similar problem and, as suggested by Idan Moshe in the update, I solved inserting a footerView in my tableView.
The solution in Swift 4:
func registerObservers(){
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil);
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidHide), name: NSNotification.Name.UIKeyboardDidHide, object: nil);
}
#objc func keyboardDidShow(_ notification: Notification) {
DispatchQueue.main.async {
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardHeight = keyboardFrame.cgRectValue.height;
let tableFooterView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: keyboardHeight))
self.tableView.tableFooterView = tableFooterView;
}
}
}
#objc func keyboardDidHide(_ notification: Notification) {
DispatchQueue.main.async {
let tableFooterView = UIView.init(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 1));
self.tableView.tableFooterView = tableFooterView;
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
let cell = self.selectedTExtField?.superview?.superview;
if let idxPath = self.tableView.indexPath(for: cell){
self.tableView.scrollToRow(at: idxPath, at: .top, animated: true);
}
else{
print("Error: index path not found");
}
}
I have UITableViewCell as shown in figure below.
The cell occupy the height occupied by delete. The cell height is set so as to keep spacing between two cell.
Now, when i swipe and delete button appears (red in color), it occupies cell height as given in picture above. I simply want to set its height to height of white part only or say the height of gray button. Can anyone help me on how to set the height of delete button that appears after swipe in UITableViewCell?
The best way to solve this was overriding
-(void)layoutSubviews in YourCustomCell:UITableViewCell
then
if ([NSStringFromClass([subview class])isEqualToString:#"UITableViewCellDeleteConfirmationControl"]){
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect buttonFrame = deleteButtonView.frame;
buttonFrame.origin.x = Xvalue;
buttonFrame.origin.y = Yvalue;
buttonFrame.size.width = Width;
buttonFrame.size.height = Height;
deleteButtonView.frame = buttonFrame;
}
Use this code in your custom Cell class
-(void) layoutSubviews
{
NSMutableArray *subviews = [self.subviews mutableCopy];
UIView *subV = subviews[0];
if ([NSStringFromClass([subV class])isEqualToString:#"UITableViewCellDeleteConfirmationView"]){
[subviews removeObjectAtIndex:0];
CGRect f = subV.frame;
f.size.height = 106; // Here you set height of Delete button
subV.frame = f;
}
}
Swift 5, works for iOS12, iOS13 and iOS14
func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
// for iOS13, iOS14
if let swipeContainerView = tableView.subviews.first(where: { String(describing: type(of: $0)) == "_UITableViewCellSwipeContainerView" }) {
if let swipeActionPullView = swipeContainerView.subviews.first, String(describing: type(of: swipeActionPullView)) == "UISwipeActionPullView" {
swipeActionPullView.frame.size.height -= 10
}
}
// for iOS12
tableView.subviews.forEach { subview in
if String(describing: type(of: subview)) == "UISwipeActionPullView" {
subview.frame.size.height -= 10
}
}
}
Add this method to your customCell.m file.
-(void) layoutSubviews
{
NSMutableArray *subviews = [self.subviews mutableCopy];
UIView *subview = subviews[0];
if ([NSStringFromClass([subview class])isEqualToString:#"UITableViewCellDeleteConfirmationView"]){
UIView *deleteButtonView = (UIView *)[subview.subviews objectAtIndex:0];
CGRect buttonFrame = deleteButtonView.frame;
buttonFrame.origin.x = deleteButtonView.frame.origin.x;
buttonFrame.origin.y = deleteButtonView.frame.origin.y;
buttonFrame.size.width = deleteButtonView.frame.size.width;
buttonFrame.size.height = 46;
deleteButtonView.frame = buttonFrame;
subview.frame=CGRectMake(subview.frame.origin.x, subview.frame.origin.y, subview.frame.size.width, 46);
deleteButtonView.clipsToBounds=YES;
subview.clipsToBounds=YES;
}
}
For IOS 13 , the Position has been yet again change , not inside table view it is once again in _UITableViewCellSwipeContainerView . Thus you should iterate through that as well.Take a look below
([NSStringFromClass([subview class])
isEqualToString:#"_UITableViewCellSwipeContainerView"]){
for (UIView *deleteButtonSubview in subview.subviews){
if ([NSStringFromClass([deleteButtonSubview class])
isEqualToString:#"UISwipeActionPullView"]) {
if ([NSStringFromClass([deleteButtonSubview.subviews[0] class]) isEqualToString:#"UISwipeActionStandardButton"]) {
//do what you want
}
}
}
}
Swift 5 - iOS 14
Change the way you are handling cell height to add spacing using the following:
override var frame: CGRect {
get {
return super.frame
}
set (newFrame) {
var frame = newFrame
frame.origin.y += 4
frame.size.height -= 10
super.frame = frame
}
}
Write below code in your custom cell hope it will work for you-
- (void)willTransitionToState:(UITableViewCellStateMask)state
{
[super willTransitionToState:state];
if(state == UITableViewCellStateShowingDeleteConfirmationMask)
{
[self performSelector:#selector(resetDeleteButtonSize) withObject:nil afterDelay:0];
}
}
- (void)resetDeleteButtonSize
{
NSMutableArray *subviews = [self.subviews mutableCopy];
while (subviews.count > 0)
{
UIView *subV = subviews[0];
[subviews removeObjectAtIndex:0];
if ([NSStringFromClass([subV class])isEqualToString:#"UITableViewCellDeleteConfirmationButton"])
{
CGRect f = subV.frame;
f.size.height = 74;
subV.frame = f;
break;
}
else
{
[subviews addObjectsFromArray:subV.subviews];
}
}
}
To see how it's work in IOS 11 please copy this Swift 4 code snippet in your UITableViewController:
override func tableView(_ tableView: UITableView, willBeginEditingRowAt indexPath: IndexPath) {
self.tableView.subviews.forEach { subview in
print("YourTableViewController: \(String(describing: type(of: subview)))")
if (String(describing: type(of: subview)) == "UISwipeActionPullView") {
if (String(describing: type(of: subview.subviews[0])) == "UISwipeActionStandardButton") {
var deleteBtnFrame = subview.subviews[0].frame
deleteBtnFrame.origin.y = 12
deleteBtnFrame.size.height = 155
// Subview in this case is the whole edit View
subview.frame.origin.y = subview.frame.origin.y + 12
subview.frame.size.height = 155
subview.subviews[0].frame = deleteBtnFrame
subview.backgroundColor = UIColor.yellow
}
}
}
}
This code working for IOS 11 and higher
SWIFT
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
if String(describing: type(of: subview.self)) == "UITableViewCellDeleteConfirmationView" {
let deleteButton = subview
let deleteButtonFrame = deleteButton.frame
let newFrame = CGRect(x: deleteButtonFrame.minX,
y: deleteButtonFrame.minY,
width: deleteButtonFrame.width,
height: yourHeight)
deleteButton.frame = newFrame
}
}
}
I have cells of varying height on a horizontally scrolling UICollectionView that appears to evenly distribute the cells vertically leaving empty space in between the cells and I would like to top align them and have the variable empty space at the bottom of each column.
I extended my UICollectionViewFlowLayout provided to my UICollectionView. The following overriding worked for me.
#import "TopAlignedCollectionViewFlowLayout.h"
const NSInteger kVerticalSpacing = 10;
#implementation TopAlignedCollectionViewFlowLayout
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSArray* attributesToReturn = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in attributesToReturn) {
if (nil == attributes.representedElementKind) {
NSIndexPath* indexPath = attributes.indexPath;
attributes.frame = [self layoutAttributesForItemAtIndexPath:indexPath].frame;
}
}
return attributesToReturn;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes* currentItemAttributes =
[super layoutAttributesForItemAtIndexPath:indexPath];
UIEdgeInsets sectionInset = [(UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout sectionInset];
if (indexPath.item == 0) {
CGRect frame = currentItemAttributes.frame;
frame.origin.y = sectionInset.top;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
NSIndexPath* previousIndexPath = [NSIndexPath indexPathForItem:indexPath.item-1 inSection:indexPath.section];
CGRect previousFrame = [self layoutAttributesForItemAtIndexPath:previousIndexPath].frame;
CGFloat previousFrameRightPoint = previousFrame.origin.y + previousFrame.size.height + kVerticalSpacing;
CGRect currentFrame = currentItemAttributes.frame;
CGRect strecthedCurrentFrame = CGRectMake(currentFrame.origin.x,
0,
currentFrame.size.width,
self.collectionView.frame.size.height
);
if (!CGRectIntersectsRect(previousFrame, strecthedCurrentFrame)) {
CGRect frame = currentItemAttributes.frame;
frame.origin.y = frame.origin.y = sectionInset.top;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
CGRect frame = currentItemAttributes.frame;
frame.origin.y = previousFrameRightPoint;
currentItemAttributes.frame = frame;
return currentItemAttributes;
}
#end
I ported this to Xamarin/MonoTouch for my project and thought it could be useful
public class TopAlignedCollectionViewFlowLayout : UICollectionViewFlowLayout
{
const int VerticalSpacing = 10;
public override UICollectionViewLayoutAttributes[] LayoutAttributesForElementsInRect(RectangleF rect)
{
var attributesToReturn = base.LayoutAttributesForElementsInRect(rect);
foreach (UICollectionViewLayoutAttributes attributes in attributesToReturn)
{
if (attributes.RepresentedElementKind == null)
{
var indexPath = attributes.IndexPath;
attributes.Frame = this.LayoutAttributesForItem(indexPath).Frame;
}
}
return attributesToReturn;
}
public override UICollectionViewLayoutAttributes LayoutAttributesForItem(NSIndexPath indexPath)
{
var currentItemAttributes = base.LayoutAttributesForItem(indexPath);
UIEdgeInsets sectionInset = ((UICollectionViewFlowLayout) this.CollectionView.CollectionViewLayout).SectionInset;
if (indexPath.Item == 0)
{
var frame1 = currentItemAttributes.Frame;
frame1.Y = sectionInset.Top;
currentItemAttributes.Frame = frame1;
return currentItemAttributes;
}
var previousIndexPath = NSIndexPath.FromItemSection(indexPath.Item - 1, indexPath.Section);
var previousFrame = this.LayoutAttributesForItem(previousIndexPath).Frame;
var previousFrameRightPoint = previousFrame.Y + previousFrame.Size.Height + VerticalSpacing;
var currentFrame = currentItemAttributes.Frame;
var strecthedCurrentFrame = new RectangleF(currentFrame.X, 0, currentFrame.Size.Width, this.CollectionView.Frame.Size.Height);
if (!previousFrame.IntersectsWith(strecthedCurrentFrame))
{
var frame = currentItemAttributes.Frame;
frame.Y = frame.Y = sectionInset.Top;
currentItemAttributes.Frame = frame;
return currentItemAttributes;
}
RectangleF frame2 = currentItemAttributes.Frame;
frame2.Y = previousFrameRightPoint;
currentItemAttributes.Frame = frame2;
return currentItemAttributes;
}
}
I imagine you are using a UICollectionViewFlowLayout to layout your cells. I don't think you can do this with a flow layout. You probably have to write a custom subclass of UICollectionViewLayout to align the top edges of each cell.
This is not a very pretty solution, but what about forcing all of your cells to be the same size and setting up the constrains in your cell to align the content to the top? This could simulate the look you are going for without having to subclass UICollectionViewLayout.