UIScrollview within UITableViewCell and maintaining scrollview page - ios

I have a UIScrollView within a UITableViewCell for being able to scroll through images within a cell. However, as a cell is reused the scroll position/content is reloaded and therefore the cell doesn't remember what scroll position (page) it was on when it comes into view again.
What's the best way to have a scroll view within a UITableView cell and have it maintain position as it comes back into view. The AirBnB app (https://itunes.apple.com/us/app/airbnb/id401626263?mt=8) seems to have accomplished this for example.

You need to keep track of your scroll views' content offset in a property. In the example below, I do this with a mutable dictionary. In cellForRowAtIndexPath:, I give the scroll view a tag and set the controller as the delegate. In the scroll view delegate method, scrollViewDidEndDecelerating:, the scroll view's content offset is set as the object for the key corresponding to the scroll view's tag. In cellForRowAtIndexPath:, I check for whether the indexPath.row (converted to an NSNumber) is one of the keys of the dictionary, and if so, restore the correct offset for that scroll view. The reason I add 1 to the tags is because the table view has its own scroll view which has a tag of 0, so I don't want to use 0 as a tag for one of the cell's scroll views.
So in cellForRowAtIndexPath, you need something like this:
cell.scrollView.tag = indexPath.row + 1;
cell.scrollView.delegate = self;
if ([self.paths.allKeys containsObject:#(indexPath.row + 1)]) {
cell.scrollView.contentOffset = CGPointMake([self.paths[#(indexPath.row + 1)] floatValue],0);
}else{
cell.scrollView.contentOffset = CGPointZero;
}
return cell;
And in the delegate method:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView.tag != 0)
[self.paths setObject:#(scrollView.contentOffset.x) forKey:#(scrollView.tag)];
}
paths is a property (NSMutableDictionary) that I create in viewDidLoad.

Use my tableView customs cells
homeScrollCell.h
#import <UIKit/UIKit.h>
#import "MyManager.h"
#interface homeScrollCell : UITableViewCell<UIScrollViewDelegate>
{
MyManager *manager;
UIScrollView *__scrollView;
}
-(void)setPage:(int)page;
#property int currentPage;
#end
homeScrollCell.m
#import "homeScrollCell.h"
#implementation homeScrollCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
manager=[MyManager sharedManager];
__scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.bounds.size.width,self.bounds.size.height)];
[__scrollView setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
NSInteger viewcount= 3;
for(int i = 0; i< viewcount; i++)
{
CGFloat x = i * self.bounds.size.width;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(x, 0,self.bounds.size.width,self.bounds.size.height)];
[view setAutoresizingMask:UIViewAutoresizingFlexibleHeight];
UILabel *label=[[UILabel alloc] initWithFrame:CGRectMake(50, 20, 200, 50)];
[label setBackgroundColor:[UIColor redColor]];
label.text=[NSString stringWithFormat:#"Hi, I am label %i",i];
[view addSubview:label];
view.backgroundColor = [UIColor greenColor];
[__scrollView addSubview:view];
}
[__scrollView setBackgroundColor:[UIColor redColor]];
__scrollView.contentSize = CGSizeMake(self.bounds.size.width *viewcount, 100);
__scrollView.pagingEnabled = YES;
__scrollView.bounces = NO;
__scrollView.delegate=self;
[self addSubview:__scrollView];
// Initialization code
}
return self;
}
-(void)setPage:(int)page
{
CGFloat pageWidth = __scrollView.frame.size.width;
float offset_X=pageWidth*page;
[__scrollView setContentOffset:CGPointMake(offset_X, __scrollView.contentOffset.y)];
_currentPage=page;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
static NSInteger previousPage = 0;
CGFloat pageWidth = scrollView.frame.size.width;
float fractionalPage = scrollView.contentOffset.x / pageWidth;
NSInteger page = lround(fractionalPage);
if (previousPage != page) {
_currentPage=page;
[manager setpage:_currentPage ForKey:[NSString stringWithFormat:#"%i",self.tag]];
previousPage = page;
}
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
#end
And use a Singleton file for keeping record or pages inside cell.
MyManager.h
#import <Foundation/Foundation.h>
#interface MyManager : NSObject
+ (id)sharedManager;
-(void)setpage:(int)page ForKey:(NSString*)key;
-(int)getpageForKey:(NSString*)key;
#end
MyManager.m
#import "MyManager.h"
static NSMutableDictionary *dictionary;
#implementation MyManager
#pragma mark Singleton Methods
+ (id)sharedManager {
static MyManager *sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
-(void)setpage:(int)page ForKey:(NSString*)key
{
[dictionary setValue:[NSString stringWithFormat:#"%i",page] forKey:key];
}
-(int)getpageForKey:(NSString*)key
{
return [[dictionary valueForKey:key] intValue];
}
- (id)init {
if (self = [super init]) {
dictionary=[[NSMutableDictionary alloc] init];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
#end
And use this custom cell in cellForRow as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Home Scroll Cell";
homeScrollCell *cell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell==nil) {
cell = [[homeScrollCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.tag=indexPath.row;
[cell setPage:[manager getpageForKey:[NSString stringWithFormat:#"%i",indexPath.row]]];
return cell;
}
Set your page inside CustomCell and scroll and after re scrolling get back to previous position will show you page set by you before scrolling.
It will persist your page moved as it is.
Download And Run

rdelmar's answer is missing something. scrollViewDidEndDecelerating will only be called if a users scroll triggers the deceleration behaviour.
A drag only scroll won't call this method so you will get inconsistent behaviour for the user who won't distinguish between the two types of scroll. You need to catch it with scrollViewDidEndDragging:willDecelerate
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
if (!decelerate) {
self.tableCellContentOffsets[#(scrollView.tag)] = #(scrollView.contentOffset.x);
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
self.tableCellContentOffsets[#(scrollView.tag)] = #(scrollView.contentOffset.x);
}

Related

How to locate the layout across view hierarchy?

I have a menuView in a list view controller. The menuView added on the UITableViewCell when a more button in the cell being taped.
I achieved the effect with singleton.
#implementation ProductsOperationMenu
static ProductsOperationMenu *_instance;
+ (instancetype)sharedInstance{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] initWithFrame:CGRectZero];
});
return _instance;
}
- (instancetype)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
[self setup];
}
return self;
}
ZBMyProductsCell.m
#implementation ZBMyProductsCell
- (void)awakeFromNib
{
[super awakeFromNib];
_operationMenu = [[ProductsOperationMenu alloc] initWithFrame: CGRectZero];
}
- (IBAction)operationButtonClick:(UIButton *)sender {
if ([self.contentView.subviews containsObject:_operationMenu]) {
_operationMenu.hidden = ![_operationMenu isHidden];
} else{
[self.contentView addSubview:_operationMenu];
_operationMenu.hidden = NO;
}
[_operationMenu mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.mas_equalTo(205);
make.height.mas_equalTo(60);
make.bottom.mas_equalTo(self.operationButton).offset(0);
make.right.mas_equalTo(self.operationButton.mas_left).offset(-10);
}];
}
Without Singleton, it became this:
So the question come.
I want to put the menuView on the controller's view, because it is unique or hidden, which used to belong to the cell.
How to convert layout of the more button selected to the controller's view?
How to use the methods to calculate?
- convertPoint:toView:
- convertPoint:fromView:
......
I did it in a simple way. Here is the code:
- (void)clickOperationButtonOfProductsCell:(ZBMyProductsCell *)myProductsCell{
NSUInteger * operationIndex = [self.myProductsTableView.visibleCells indexOfObject:myProductsCell];
CGFloat originY = operationIndex.row * 110 + 50 + 40;
CGRect originFrame = CGRectMake(KScreenWidth - 55, originY, 0, 60);
CGRect finalFrame = CGRectMake(KScreenWidth - 260, originY, 205, 60);
self.operationMenuView.frame = originFrame;
[UIView animateWithDuration: 0.5 delay: 0 options: UIViewAnimationOptionCurveEaseIn animations:^{
self.operationMenuView.frame = finalFrame;
} completion:^(BOOL finished) { }];
}
How to achieve it more adaptively?
Maybe you can try it like this:
// here is your cell where the more button belongs to
#interface ZBMyProductsCell: UITableViewCell
#property (nonatomic, copy) void(^moreAction)(ZBMyProductsCell *cell);
#end
#implementation ZBMyProductsCell
- (void)_moreButtonClicked:(UIButton *)sender {
!self.moreAction ?: self.moreAction(self);
}
#end
// here is your view controller where your table view belongs to
// this is a `UITableViewDataSource` delegate method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ZBMyProductsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ZBMyProductsCell" forIndexPath:indexPath];
// Configure the cell...
cell.moreAction = ^(ZBMyProductsCell *cell) {
CGRect rect = [tableView rectForRowAtIndexPath:indexPath];
// write your own code to show/hide the menu
};
return cell;
}
Create a variable in each cell model called cellIndexpath and in cellForRow init it
cell.cellIndexpath = indexpath
have a look of UIPopoverPresnetationController and see if it can do the job. It should be available on iPhone. Putting menu view on the controller’viewwill cause issue when you scroll the table view.
use
UIMenuController *menu = [UIMenuController sharedMenuController];
[menu setTargetRect:self.detaiLabel.frame inView:self];

App crashes while reloading and scrolling tableview one direction

I have a UITableView for an Instagram feed. I have implemented UIRefreshControl for pull to refresh functionality.
After drag and release the refresh control, and while the spinner is going, I'm able to drag the tableview down without the app crashing. However, if I scroll the tableview upwards, the app crashes (maybe because of cell 2, 3 etc?)
Here's a video showing the problem: http://www.screenmailer.com/v/DukT4lt2aUGm8c5MLRlGMg/2586/3a23tXo7uXs.mp4
Why is this happening?
Code for the .m file:
#import "InstagramViewController.h"
#import "InstagramCell.h"
#import <InstagramKit/InstagramKit.h>
#import "UIImageView+AFNetworking.h"
#interface InstagramViewController ()
{
NSMutableArray *mediaArray;
}
#property (nonatomic, strong) InstagramPaginationInfo *currentPaginationInfo;
#end
#implementation InstagramViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
//mediaArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)viewDidLoad
{
mediaArray = [[NSMutableArray alloc] init];
[super viewDidLoad];
[self loadMedia];
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:#selector(reloadMedia) forControlEvents:UIControlEventValueChanged];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
-(void)reloadMedia
{
self.currentPaginationInfo = nil;
[mediaArray removeAllObjects];
[self loadMedia];
}
-(IBAction)loadMedia
{
// start network indicator
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
[[InstagramEngine sharedEngine] getMediaWithTagName:#"AUFsommer" count:10 maxId:self.currentPaginationInfo.nextMaxId withSuccess:^(NSArray *media, InstagramPaginationInfo *paginationInfo) {
if (paginationInfo)
{
self.currentPaginationInfo = paginationInfo;
}
[mediaArray addObjectsFromArray:media];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
[self reloadData];
} failure:^(NSError *error) {
NSLog(#"Search Media Failed");
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
}];
}
-(void)reloadData
{
[self.refreshControl endRefreshing];
[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *headerView = [[UIView alloc] init];
headerView.backgroundColor = [UIColor colorWithWhite:1.0f alpha:0.9f];
InstagramMedia *media = mediaArray[section];
// create imageview for profile photo
AsyncImageView *profilePhoto = [[AsyncImageView alloc] initWithFrame:CGRectMake(8, 8, 32, 32)];
profilePhoto.layer.borderColor = [[UIColor colorWithRed:204.0/255.0f green:204.0/255.0f blue:204.0/255.0f alpha:1.0f] CGColor];
profilePhoto.layer.borderWidth = 1;
profilePhoto.layer.masksToBounds = YES;
profilePhoto.layer.cornerRadius = 16.0;
[profilePhoto loadImageFromURL:[media.user.profilePictureURL absoluteString]];
// uifont settings
UIFont *labelFont = [UIFont boldSystemFontOfSize:13.0];
// create label for username
UILabel *usernameLabel = [[UILabel alloc] initWithFrame:CGRectMake(48, 0, 210, 48)];
usernameLabel.text = media.user.username;
usernameLabel.font = labelFont;
usernameLabel.textColor = [UIColor colorWithRed:235.0/255.0 green:24.0/255.0 blue:22.0/255.0 alpha:1.0f];
// create label for timestamp
UILabel *timestampLabel = [[UILabel alloc] initWithFrame:CGRectMake(250, 0, 54, 48)];
timestampLabel.textAlignment = NSTextAlignmentRight;
timestampLabel.font = labelFont;
// timestampLabel.text = [self stringForDisplayFromDate:media.createdDate];
// add to view
[headerView addSubview:profilePhoto];
[headerView addSubview:usernameLabel];
[headerView addSubview:timestampLabel];
return headerView;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return mediaArray.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
InstagramCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[InstagramCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// clear photo
[cell.igPhoto setImage:nil];
if (mediaArray.count >= indexPath.section+1)
{
InstagramMedia *media = mediaArray[indexPath.section];
cell.title.text = media.caption.text;
[cell.igPhoto loadImageFromURL:[media.standardResolutionImageURL absoluteString]];
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
InstagramMedia *media = mediaArray[indexPath.section];
CGSize maximumLabelSize = CGSizeMake(304.0f, 20000.0f);
CGSize expectedLabelSize = [media.caption.text sizeWithFont:[UIFont systemFontOfSize:13.0f] constrainedToSize:maximumLabelSize lineBreakMode:NSLineBreakByWordWrapping];
return (320.0f + expectedLabelSize.height + 20.0f);
}
-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
CGFloat currentOffset = scrollView.contentOffset.y;
CGFloat maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;
if (maximumOffset - currentOffset < 10.0)
{
[self loadMedia];
}
}
#end
The first thing you should do is to add an exception breakpoint in your project, so you will know why and where it crashes.
The reason why your app is crashing is that you try to read in an empty array. In the method reloadMedia, you do this :
self.currentPaginationInfo = nil;
[mediaArray removeAllObjects];
So at this point, your array is empty, but your UITableView is not aware of that. By scrolling before your data is reloaded, the methods cellForRowAtIndexPath will get called and try to access an index in your empty array.
To fix this, you can call [self.tableView reloadData]; after [mediaArray removeAllObjects];. This way, the UITableView will makes its call to know how many rows and sections it should have and will be aware there is no more rows and sections.
Or, if you want the old information to still be in your UITableView during the loading, just don't nil the currentPaginationInfo or empty mediaArray in reloadMedia,

Enable and Disable UITableview scroll based on UITableview cell movement on dragging

I am creating one application based on FMMoveTableView where I have to drag cell on long press and change its position with in same section and different section.The cell is dragging fine and setting in the same and different section.But the problem is when I start dragging the cell upwards the table also starts scrolling up.So some of its cells are invisible because of bounce where we want to keep the dragged cell.The same thing is happening when I drag the cell to the bottom.
Is it anything related to UITableView property or I have to do it programmatically?
The app FMMoveTableView which I followed for this functionality,it is working fine where it is using UITableView class type.I implemented it in UIViewController class where I made some other views.
UITableView Properties:
self.GroupedTableView=[[UITableView alloc]initWithFrame:CGRectMake(20, 25, 280, 480) style:UITableViewStylePlain];
self.GroupedTableView.showsHorizontalScrollIndicator=YES;
self.GroupedTableView.showsVerticalScrollIndicator=YES;
self.GroupedTableView.bounces=YES;
self.GroupedTableView.alwaysBounceHorizontal=NO;
self.GroupedTableView.alwaysBounceVertical=YES;
self.GroupedTableView.bouncesZoom=YES;
self.GroupedTableView.delaysContentTouches=YES;
self.GroupedTableView.canCancelContentTouches=YES;
self.GroupedTableView.userInteractionEnabled=YES;
self.GroupedTableView.dataSource=self;
self.GroupedTableView.delegate=self;
self.GroupedTableView.rowHeight=30;
self.GroupedTableView.backgroundColor=[UIColor clearColor];
self.GroupedTableView.tag=202;
[self.view addSubview:self.GroupedTableView];
Long Press Gesture:
UILongPressGestureRecognizer *movingGestureRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPress:)];
[movingGestureRecognizer setDelegate:self];
[self.GroupedTableView addGestureRecognizer:movingGestureRecognizer];
Auto Scroll Methods:
- (void)legalizeAutoscrollDistance
{
float minimumLegalDistance = [self.GroupedTableView contentOffset].y * -1;
float maximumLegalDistance = [self.GroupedTableView contentSize].height - ([self.GroupedTableView frame].size.height + [self.GroupedTableView contentOffset].y);
[self setAutoscrollDistance:MAX([self autoscrollDistance], minimumLegalDistance)];
[self setAutoscrollDistance:MIN([self autoscrollDistance], maximumLegalDistance)];
}
- (void)stopAutoscrolling
{
[self setAutoscrollDistance:0];
[[self autoscrollTimer] invalidate];
[self setAutoscrollTimer:nil];
}
- (void)maybeAutoscrollForSnapShotImageView:(FMSnapShotImageView *)snapShot
{
[self setAutoscrollDistance:0];
NSLog(#"Height====%f",[self.GroupedTableView frame].size.height);
NSLog(#"Height====%f",[self.GroupedTableView contentSize].height);
NSLog(#"Frame====%#",NSStringFromCGRect([snapShot frame]));
NSLog(#"Frame====%#",NSStringFromCGRect([self.GroupedTableView bounds]));
// Check for autoscrolling
// 1. The content size is bigger than the frame's
// 2. The snap shot is still inside the table view's bounds
if ([self.GroupedTableView frame].size.height < [self.GroupedTableView contentSize].height && CGRectIntersectsRect([snapShot frame], [self.GroupedTableView bounds]))
{
CGPoint touchLocation = [[self movingGestureRecognizer] locationInView:self.GroupedTableView];
touchLocation.y += [self touchOffset].y;
float distanceToTopEdge = touchLocation.y - CGRectGetMinY([self.GroupedTableView bounds]);
float distanceToBottomEdge = CGRectGetMaxY([self.GroupedTableView bounds]) - touchLocation.y;
if (distanceToTopEdge < [self autoscrollThreshold])
{
[self setAutoscrollDistance:[self autoscrollDistanceForProximityToEdge:distanceToTopEdge] * -1];
}
else if (distanceToBottomEdge < [self autoscrollThreshold])
{
[self setAutoscrollDistance:[self autoscrollDistanceForProximityToEdge:distanceToBottomEdge]];
}
}
if ([self autoscrollDistance] == 0)
{
[[self autoscrollTimer] invalidate];
[self setAutoscrollTimer:nil];
}
else if (![self autoscrollTimer])
{
NSTimer *autoscrollTimer = [NSTimer scheduledTimerWithTimeInterval:(1.0 / 60.0) target:self selector:#selector(autoscrollTimerFired:) userInfo:snapShot repeats:YES];
[self setAutoscrollTimer:autoscrollTimer];
}
}
- (void)autoscrollTimerFired:(NSTimer *)timer
{
[self legalizeAutoscrollDistance];
CGPoint contentOffset = [self.GroupedTableView contentOffset];
contentOffset.y += [self autoscrollDistance];
[self.GroupedTableView setContentOffset:contentOffset];
// Move the snap shot appropriately
FMSnapShotImageView *snapShot = (FMSnapShotImageView *)[timer userInfo];
[snapShot moveByOffset:CGPointMake(0, [self autoscrollDistance])];
// Even if we autoscroll we need to update the moved cell's index path
CGPoint touchLocation = [[self movingGestureRecognizer] locationInView:self.GroupedTableView];
[self moveRowToLocation:touchLocation];
}
- (float)autoscrollDistanceForProximityToEdge:(float)proximity
{
return ceilf(([self autoscrollThreshold] - proximity) / 5.0);
}
I am unable to stop tableview scroll when I drag a cell.What I need that table should not move till the dragged cell has not reached to the top or bottom and then it should scroll to show hidden Cells.
// // ViewController.h // testingApp
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
UILongPressGestureRecognizer *reco; }
#property (nonatomic, weak) IBOutlet UITableView *table;
#end
//
// ViewController.m
// testingApp
#import "ViewController.h"
#implementation ViewController
#synthesize table;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
reco = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(recognize:)];
[self.table addGestureRecognizer:reco];
}
-(void)recognize:(id)sender
{
NSLog(#"recognize");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 5;
}
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
return cell;
}
#end
I have got the solution.Actually I am using some gestures on my table view cell.So to enable this along with other gestures I was using:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
So this was actually activating the UITableView gestures too when it was not needed.And so When I dragged the cell Image my table also started scrolling along with the image.I misinterpreted it as my implementation for row sliding has got some issue.So code used in question works is fine if someone needs it in future.What I did is added some conditions in the above method and activated it when it was needed.

How to add a custom separator to UITableViewCell?

Please spare sometime as this is a long explanation
I have a UIViewController which consists of a UIButton and a UITableView which loads different types of UITableViewCells with Identifier Cell1 and Cell2, on event touchUpInside of the button. I m using storyboard.
The separator for both cells are customized.
Cell1 has a separator that occupies the entire width of cell and 1 pixel height at the bottom of the cell.
Whereas Cell2 has a separator which has offset of 5 pixels from the cell, both left and right.
Each time the button outside the tableView is clicked the tableViewCells are swapped, based on the cell identifier.
Initially the tableView occupies the complete width of viewController and consists of Cell1, but the the button is tapped , tableViewCells are changed to Cell2 and the frame of the tableView is Changed, The width is reduced by 10 and x-origin is increased by 5.
But when this happens, the separator of Cell2 is 5 pixels away from the cell on right but on the left it is away by 5 pixel.
This happens for all Cell2 which is loaded with data, and the cells which has no data the frame is changed appropriately.
But the cell after that has the width of Cell1 (larger width)
-(void)setSeperatorStyleForTableView :(UITableViewCell *)cell //this is called in cellForRowAtIndex
{
//cell- type of cell(Cell1 or Cell2)
CGRect seperatorFrame;
UIImageView *seperatorImage;
seperatorFrame = [self setSeperatorFrame:cell];
if(firstCellToBeLoaded)//BOOL used to change the button text and load appropriate cells
{
seperatorImage = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"table_row
2.png"]];
}
else
{
seperatorImage = [[UIImageView alloc] initWithImage:[UIImage
imageNamed:#"table_row.png"]];
}
seperatorImage.frame = seperatorFrame;
seperatorImage.autoresizingMask = YES;
[cell.contentView addSubview:seperatorImage];
}
//set the customized separator frame
-(CGRect)setSeperatorFrame :(UITableViewCell *)cell
{
CGRect seperatorFrame;
seperatorFrame.size.height = 1.0;
seperatorFrame.origin.y = cell.frame.origin.y + (cell.frame.size.height - 1.0);
if(firstCellToBeLoaded)
{
seperatorFrame.origin.x = cell.frame.origin.x ;
seperatorFrame.size.width = cell.frame.size.width;
}
else
{
seperatorFrame.origin.x = cell.frame.origin.x + 5.0;
seperatorFrame.size.width = cell.frame.size.width -10.0;
}
return seperatorFrame;
}
You can add tableView's standard separator line, and add your custom line at the top of each cell.
In following code Change hight/width/color/image of UIView for set your separatorLine.
The easiest way to add custom separator is to add simple UIView of 1px height:
UIView* separatorLineView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 1)];/// change size as you need.
separatorLineView.backgroundColor = [UIColor grayColor];// you can also put image here
[cell.contentView addSubview:separatorLineView];
This code might solve your problem :)
Right way to do this would be to have the separator with in the cell class, subclass UITableViewCell if you haven't add a separator image variable there and on each cell creation you can just change the image and the frame rather than adding that on each redraw. If you require a code for this i can supply that as well. Currently when the cell is redrawn it already has the image u added last time and you just adding that again, either you remove that in -prepareForReuse method or just do as i explained above.
***** Custom Cell *****
//
// CustomCell.m
// Custom
//
// Created by Syed Arsalan Pervez on 2/8/13.
// Copyright (c) 2013 SAPLogix. All rights reserved.
//
#import "CustomCell.h"
#implementation CustomCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
_separatorImage = [[UIImageView alloc] initWithFrame:CGRectZero];
[[self contentView] addSubview:_separatorImage];
}
return self;
}
- (void)prepareForReuse
{
_separatorImage.image = nil;
}
- (void)dealloc
{
[_separatorImage release];
[super dealloc];
}
#end
Using the above cell in the view controller.
***** Test View Controller *****
//
// TestViewController.m
// Custom
//
// Created by Syed Arsalan Pervez on 2/8/13.
// Copyright (c) 2013 SAPLogix. All rights reserved.
//
#import "TestViewController.h"
#import "CustomCell.h"
#interface TestViewController ()
#end
#implementation TestViewController
- (void)viewDidLoad
{
[self.tableView setSeparatorStyle:UITableViewCellSeparatorStyleNone];
#warning TODO: set the image name here
_separatorImage1 = [[UIImage imageNamed:#""] retain];
_separatorImage2 = [[UIImage imageNamed:#""] retain];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *_identifier = #"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:_identifier];
if (!cell)
{
cell = [[[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:_identifier] autorelease];
}
//Set Separator Image Here
//Preload the image so it doesn't effect the scrolling performance
CGRect frame = cell.contentView.frame;
switch (indexPath.row)
{
case 0:
cell.separatorImage.image = _separatorImage1;
cell.separatorImage.frame = CGRectMake(0, CGRectGetMaxY(frame)-1, frame.size.width, 1);
break;
case 1:
cell.separatorImage.image = _separatorImage2;
cell.separatorImage.frame = CGRectMake(frame.origin.x+5, CGRectGetMaxY(frame)-1, frame.size.width-10, 1);
break;
}
return cell;
}
- (void)dealloc
{
[_separatorImage1 release];
[_separatorImage2 release];
[super dealloc];
}
#end
I do it this way... hope this might help.
//
// UITableViewCell+MyAdditions.h
//
// Created by Roberto O. Buratti on 19/02/14.
//
#import <UIKit/UIKit.h>
#interface UITableViewCell (MyAdditions)
#property (nonatomic,assign) UITableViewCellSeparatorStyle cellSeparatorStyle;
#property (nonatomic,strong) UIColor *cellSeparatorColor;
#end
//
// UITableViewCell+MyAdditions.m
//
// Created by Roberto O. Buratti on 19/02/14.
//
#import "UITableViewCell+MyAdditions.h"
NSString *const kUITablewViewCellSeparatorLayerName = #"kUITablewViewCellSeparatorLayerName";
#implementation UITableViewCell (MyAdditions)
-(CALayer *)separatorLayer
{
for (CALayer *sublayer in self.layer.sublayers)
{
if ([sublayer.name isEqualToString:kUITablewViewCellSeparatorLayerName])
return sublayer;
}
return nil;
}
-(CALayer *)newSeparatorLayer
{
CALayer *separatorLayer = [CALayer layer];
separatorLayer.name = kUITablewViewCellSeparatorLayerName;
separatorLayer.frame = CGRectMake(0, self.bounds.size.height - 1, self.bounds.size.width, 1);
separatorLayer.backgroundColor = [UIColor whiteColor].CGColor;
[self.layer addSublayer:separatorLayer];
return separatorLayer;
}
-(UITableViewCellSeparatorStyle)cellSeparatorStyle
{
CALayer *separatorLayer = [self separatorLayer];
if (separatorLayer == nil)
return UITableViewCellSeparatorStyleNone;
else
return UITableViewCellSeparatorStyleSingleLine;
}
-(void)setCellSeparatorStyle:(UITableViewCellSeparatorStyle)separatorStyle
{
CALayer *separatorLayer = [self separatorLayer];
switch (separatorStyle)
{
case UITableViewCellSeparatorStyleNone:
[separatorLayer removeFromSuperlayer];
break;
case UITableViewCellSeparatorStyleSingleLine:
if (separatorLayer == nil)
separatorLayer = [self newSeparatorLayer];
break;
default:
#throw [NSException exceptionWithName:NSStringFromClass([self class]) reason:#"Unsupported separatorStyle" userInfo:nil];
break;
}
}
-(UIColor *)cellSeparatorColor
{
CALayer *separatorLayer = [self separatorLayer];
return [UIColor colorWithCGColor:separatorLayer.backgroundColor];
}
-(void)setCellSeparatorColor:(UIColor *)separatorColor
{
CALayer *separatorLayer = [self separatorLayer];
if (separatorLayer == nil)
separatorLayer = [self newSeparatorLayer];
separatorLayer.backgroundColor = separatorColor.CGColor;
}
#end
Now you can do things like
UITableViewCell *cell = ...
cell.cellSeparatorStyle = UITableViewCellSeparatorStyleSingleLine;
cell.cellSeparatorColor = [UIColor orangeColor];
As others said, usually there's two ways to do this, either create a CALayer or a UIView separator of 1px and add it to contentView.
Over time I've seen multiple projects do this differently, and sometimes, even multiple different ways in the same project. It's also easy to introduce bugs because of cell reuse and also, for proper rendering of pixel lines, one must incorporate the screen scale: (1.0 / [UIScreen mainScreen].scale).
I've created a library that simplifies this to just a single method class, with no subclassing required.
https://github.com/kgaidis/KGViewSeparators
Objective-C:
[view kg_show:YES separator:KGViewSeparatorTop color:[UIColor blackColor] lineWidth:KGViewSeparatorLineWidth(1.0) insets:UIEdgeInsetsMake(0, 15.0, 0, 15.0)];
Swift:
view.kg_show(true, separator: .Bottom, color: UIColor.blackColor(), lineWidth: KGViewSeparatorLineWidth(1.0), insets: UIEdgeInsetsZero)

iOS partial screen dialog for drop down listbox (android spinner) style control

In iOS for the iPhone I want to make a control with similar appearance and behavior to the android spinner control when configured to behave like a drop down list box. Specifically when pressed a modal list of text options with radio buttons comes up and when one of them is pressed the list disappears and the control updates to that choice. Example:
So far I have seen a full-screen option using [self presentViewController...] with a custom ViewController but I want a partial screen (like pictured above) solution. Does anyone know how to do this or could point in the right direction.
The native solution to this will be a UIActionSheet which on iPhone will appear from the bottom and be partial screen or on iPad be very similar to the android version.
You can find the documentation here: UIActionSheet
if you didnt want to use the UIActionSheet and you wanted to make it reusable rather than adding a whole bund of UIViews to your current XIB, you could create a custom UIView with whatever interface you would need to populate it and use the interface builder to help make it look ok.
that view could have a message handler that posts the response that you would need to listen for.
then just init and load the view into your subviews and populate it
then post a message from the custom view to the handler you registered
so for your custom view you would have something like this.
#implementation SomeCustomView
+(SomeCustomView*)viewFromNibNamed:(NSString *)nibName{
NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
SomeCustomView *customView = nil;
NSObject* nibItem = nil;
while ((nibItem = [nibEnumerator nextObject]) != nil) {
if ([nibItem isKindOfClass:[AADropDown class]]) {
customView = (SomeCustomView*)nibItem;
break;
}
}
return customView;
}
-(void)someInitializationWith:(NSArray*)repeatableData andNotificationId:(NSString*)noteId{
//set your stuff up for the view here and save the notification id
}
...
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[[NSNotificationCenter defaultCenter] postNotificationName:Your_Notification_Id object:somevalue];
}
#end
and include other things, like in this case the tableview stuff or any other logic.
then in your viewcontroller you could call it like
__block id observer = [[NSNotificationCenter defaultCenter] addObserverForName:#"customViewAction" object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification *note) {
//deal with notification here
[[NSNotificationCenter defaultCenter] removeObserver: observer];
}];
SomeCustomView *cv =(SomeCustomView*) [SomeCustomView viewFromNibNamed:#"SomeCustomView"];
[cv someInitializationWith:arrayOptions andNotificationId:#"customViewAction"];
[self.view addSubview:cv];
and in your interface builder you will just need to make sure that the class of the view is set to your class type.
then you can easily reuse this code again whenever a user needs to select something else in the same manner.
Here is a variation on the solution suggested by AtomRiot.
On your view (xib or storyboard) make a button and assign this graphic to it. Don't worry if it appears stretched out in the editor. The code will make it a realizable graphic.
2X version
Then include the following files in your project (copied below):
DDLBHelper.h
DDLBHelper.m
Then in your ViewController's .h file make links to the button:
#property (weak, nonatomic) IBOutlet UIButton *ddlbB;
- (IBAction)ddlbBClick:(id)sender;
In you ViewController's .m file make the following calls:
#synthesize ddlbB, choiceLabel;
DDLBHelper *mDDLBH;
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *strings = [[NSArray alloc] initWithObjects:#"Item 1", #"Item 2", #"Item 3", nil];
mDDLBH = [[DDLBHelper alloc] initWithWithViewController:self button:ddlbB stringArray:strings currentValue:1];
}
- (IBAction)ddlbBClick:(id)sender {
[mDDLBH popupList];
}
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
[mDDLBH adjustToRotation];
}
Works just like android.
Here are the files:
DDLBHelper.h
// DDLBHelper.h
// Created by MindSpiker on 9/27/12.
#import <Foundation/Foundation.h>
#protocol DDLBHelperDelegate <NSObject>
#required
- (void) itemSelected: (int)value;
#end
#interface DDLBHelper : UIViewController <UITableViewDelegate, UITableViewDataSource>{
id <DDLBHelperDelegate> delegate;
}
#property (retain) id delegate;
// external interface
- (id) init;
- (id) initWithWithViewController:(UIViewController *)viewController button:(UIButton *)button stringArray:(NSArray *)values currentValue:(int) currentValue;
- (void) popupList;
- (BOOL) isShown;
- (void) adjustToRotation;
- (int) getValue;
- (NSString *)getValueText;
#end
DDLBHelper.m
// DDLBHelper.m
// Created by MindSpiker on 9/27/12.
#import "DDLBHelper.h"
#import <QuartzCore/QuartzCore.h>
#interface DDLBHelper () {
#private
UIViewController *mVC;
UIButton *mButton;
NSArray *mValues;
int mValue;
UITableView *mTV;
UIView *mBackgroundV;
}
#end
#implementation DDLBHelper
#synthesize delegate;
- (id) init {
self = [super init];
mVC = nil;
mButton = nil;
mValues = nil;
mValue = -1;
return self;
}
- (id) initWithWithViewController:(UIViewController *)viewController button:(UIButton *)button stringArray:(NSArray *)values currentValue:(int) currentValue {
self = [super init];
// save pointers
mVC = viewController;
mButton = button;
mValues = values;
mValue = currentValue;
[self setupButton];
return self;
}
- (void) popupList{
if (mBackgroundV == nil){
mBackgroundV = [self setupBackgroundView];
[mVC.view addSubview:mBackgroundV];
}
if (mTV == nil){
mTV = [self setupTableView];
[mVC.view addSubview:mTV];
}
[mTV reloadData];
[mBackgroundV setHidden:NO];
[mTV setHidden:NO];
}
- (BOOL) isShown{
return !mTV.isHidden;
}
- (void) adjustToRotation{
BOOL isShown = [self isShown];
// remove the controls
if (mBackgroundV != nil){
[mBackgroundV removeFromSuperview];
mBackgroundV = nil;
}
if (mTV != nil){
[mTV removeFromSuperview];
mTV = nil;
}
if (isShown){
[self popupList];
}
}
- (int) getValue{
return mValue;
}
- (NSString *) getValueText{
if (mValues != nil && mValue > -1) {
if (mValues.count > mValue){
return [mValues objectAtIndex:mValue];
}
}
return nil;
}
- (void) updateButtonTitle{
NSString *title = [NSString stringWithFormat:#" %#", [self getValueText]];
[mButton setTitle:title forState:UIControlStateNormal];
}
- (void) setupButton {
UIImage *buttonBG = [UIImage imageNamed:#"sis_proceeds_ddlb.png"];
UIEdgeInsets insets = UIEdgeInsetsMake(8, 8, 8, 45);
UIImage *sizableImg = [buttonBG resizableImageWithCapInsets:insets];
[mButton setBackgroundImage:sizableImg forState:UIControlStateNormal];
[mButton setContentHorizontalAlignment:UIControlContentHorizontalAlignmentLeft];
[self updateButtonTitle];
}
- (UIView *) setupBackgroundView{
UIView *v = [[UIView alloc] initWithFrame:mVC.view.bounds];
[[v layer] setOpaque:NO];
[[v layer] setOpacity:0.7f];
[[v layer] setBackgroundColor:[UIColor blackColor].CGColor];
return v;
}
- (UITableView *) setupTableView {
CGRect rect = [self makeTableViewRect];
UITableView *tv = [[UITableView alloc] initWithFrame:rect style:UITableViewStylePlain];
[tv setDelegate:self];
[tv setDataSource:self];
[tv setBackgroundColor:[UIColor whiteColor]];
[[tv layer] setBorderWidth:2];
[[tv layer] setBorderColor:[UIColor lightGrayColor].CGColor];
[[tv layer] setCornerRadius:10];
[mVC.view addSubview:tv];
return tv;
}
- (CGRect) makeTableViewRect {
float l=0.0, t=0.0, w=0.0, h=0.0, maxH=0.0, cellH=0.0, cellsH=0.0;
// get
l = mButton.frame.origin.x;
w = mButton.frame.size.width;
t = mVC.view.bounds.origin.y + 50;
maxH = mVC.view.bounds.size.height - 100;
// get cell height
UITableViewCell *c = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
cellH = c.bounds.size.height;
// see if list will overlow maxH(eight)
cellsH = cellH * mValues.count;
if (cellsH > maxH) {
h = maxH;
} else {
h = cellsH;
}
return CGRectMake(l, t, w, h);
}
#pragma mark - TableView Delegate functions
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return 1; // this is a one section table
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return mValues.count; // should be called for only one section
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
// try to resuse a cell if possible
static NSString *RESUSE_IDENTIFIER = #"myResuseIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:RESUSE_IDENTIFIER];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RESUSE_IDENTIFIER];
}
cell.textLabel.text = [mValues objectAtIndex:indexPath.row];
if (mValue == indexPath.row){
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
// save value and hide view
mValue = indexPath.row;
[self updateButtonTitle];
[mBackgroundV setHidden:YES];
[mTV setHidden:YES];
[delegate itemSelected:mValue];
}
#end

Resources