Totally Confused !!!...
I have one web-service that accepts the pageNumber as Parameter and gives me some records as JSON response. (This service basically implements the functionality of Paging for Silverlight version.) I want to load these pages one by one when user scrolls the UITableView (means Load the next 20 entries (or second page) when user scrolls).
My Problem :
How and Where to call this service and How to calculate pageNumber ?
I searched about this but didn't get any Satisfied answer.
What I found is :
How to call web service after each certain number of data received and load it into table view
Load more data from a web service when the user scrolls the UITableView
how to add elements to tableview on scrolling iphone?
I would love to here your responses in Objective-C Language.
Any Suggestions ?
Take one integer set it to 0. Now Used Pull to refresh functionality (Pull To refresh exmaple).
Now in API side you have to set the two extra parameter like pagenumber and pageSize
At very first time you call API with pageNumber=0 and pageSize=20
Now when you pull the table then you have a particular method in which you have to call the API with pageNumber++ and pageSize=20 again and whatever you get in the response add into your NSMutableArray. If you found nothing then remove pull to refresh option
I solved my Question with this Code :
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGPoint offset = scrollView.contentOffset;
CGRect bounds = scrollView.bounds;
CGSize size = scrollView.contentSize;
UIEdgeInsets inset = scrollView.contentInset;
float y = offset.y + bounds.size.height - inset.bottom;
float h = size.height;
float reload_distance = 10;
if(y > h + reload_distance)
{
lastPageNumber++;
[[NSUserDefaults standardUserDefaults] setInteger:lastPageNumber forKey:#"PageNumber"];
// Call the method.
}
}
Hope, it will be helpful for future Visitors.
To make an infinite scroller, you need to know when to start fetching the new items. There are many ways to do this, but the best place is to hook into the UITableViewDelegate's methods. When cellForRowAtIndexPath: is called, check if the row is the last in your data store. If it is, make the call for the new data, then append it to the old data and refresh your view. You can also hook into the table views scroller to see when it it X percent scrolled to the bottom to make this call.
In .h :
int lastPageNumber;
In .m :
In cellForRowAtIndexPath delegate method
int rowNum =indexPath.row;
if(rowNum%20==0)
{
if(rowNum/20>lastPageNumber)
{
//call webService
lastPageNumber++;
}
}
Related
I am using ShinobiCharts in my iOS app to plot a line chart. This requires a feature where in the default view will be in Days. When i pinch zoom, i will be getting Weeks data, and more pinch zooming will give me Months data. Same applies for zoom out in reverse order.
I am not able to find a way to show this data at different zoom levels.
Please help me with this.
Im using following delegate method to check zoom level
- (void)sChartIsZooming:(ShinobiChart *)chart withChartMovementInformation:
(const SChartMovementInformation *)information;
but i dont find any way to check zoom levels.
One way of checking this is to determine the number of days currently displayed within the axis' visible range.
First off you'll need a way to record the current granularity of data on display in the chart:
typedef NS_ENUM(NSUInteger, DataView)
{
DataViewDaily,
DataViewWeekly,
DataViewMonthly,
};
The initial view will be DataViewDaily and is assigned within viewDidLoad to the property currentDataView.
Then within sChartIsZooming:withChartMovementInformation: you could do:
- (void)sChartIsZooming:(ShinobiChart *)chart withChartMovementInformation:(const SChartMovementInformation *)information
{
// Assuming x is our independent axis
CGFloat span = [_chart.xAxis.axisRange.span doubleValue];
static NSUInteger dayInterval = 60 * 60 * 24;
NSUInteger numberOfDaysDisplayed = span / dayInterval;
DataView previousDataView = _currentDataView;
if (numberOfDaysDisplayed <= 7)
{
// Show daily data
_currentDataView = DataViewDaily;
}
else if (numberOfDaysDisplayed <= 30)
{
// Show weekly data
_currentDataView = DataViewWeekly;
}
else
{
// Show monthly data
_currentDataView = DataViewMonthly;
}
// Only reload if the granularity has changed
if (previousDataView != _currentDataView)
{
// Reload and redraw chart to show new data
[_chart reloadData];
[_chart redrawChart];
}
}
Now within your datasource method sChart:dataPointAtIndex:forSeriesAtIndex: you can return the appropriate data points by switching on the value of _currentDataView.
Note that you may need to also update sChart:numberOfDataPointsForSeriesAtIndex to return the number of points to display at the current view level.
I'm making collection view with a dynamic content loading ability. In my case, I'm making calendar. Current date will be in the visible part (after loading). If you scroll up, the older dates will shown and if you scroll down you'll see next dates.
My collection view use standard flow layout. I have 7 cells in a row and 4 visible rows.
I have a problem with adding older dates (e.g. cells that appears if you scroll up). What I'm doing: first I implement scroll view method for detecting scroll event.
startOffsetY is contentOffset.y value set in viewDidLoad. It's not equal to 0 because I set contentInset. upd is just a flag that means that new update is could be start.
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.y <= startOffsetY - 20 && upd {
calendar.updateDataSource()
}
}
Next, I calculate dates for previous time slice (about 4 weeks, I also tried 1 week) from the first date that is in my data source array.
After that, I calculate indexPath array and make update to collection view:
var indexes = [NSIndexPath]()
for n in 1...4 {
for i in reverse(1...7) {
indexes.append(NSIndexPath(forItem: n * 7 - i, inSection: 0))
}
}
self.calendarCollectionView.performBatchUpdates({ () -> Void in
self.calendarCollectionView.insertItemsAtIndexPaths(indexes)
}, completion: { (finish) -> Void in
self.upd = true
})
but, I have visible lags when rows added and scrolling is in progress.
I tried different strategies: I used reloadData() and it was ideal (on the simulator) and extremely laggy on my iPhone 4S (this was the first time in my experience when simulator was faster than the device). From this point, I figure out, that animation of inserting items might be the problem. I tried to wrap performBatchUpdates into the UIView.performWithoutAnimation block, but with no luck also.
I'm really looking for some help, I don't looking for a ready made solutions except they work as I describe (scroll up'n'down and load content) and I can look how it works. Once again, scrolling already loaded items is not a problem, the problem is a lagging when content is add at the begging of my data array.
EDIT
Code provided by #teamnorge in Swift
func scrollViewDidScroll(scrollView: UIScrollView) {
let currentOffset = scrollView.contentOffset.y
let contentHeight = scrollView.contentSize.height
var offset : CGFloat!
println(currentOffset)
/* 0.125 - 1/8, 0.5 - 1/2 */
if currentOffset < contentHeight * 0.125 {
offset = contentHeight * 0.125 - currentOffset
// reload content here
scrollView.contentOffset = CGPoint(x: 0, y: currentOffset + contentHeight * 0.5 + offset - CGFloat(cellHeight))
}
/* 0.75 - 6/8, 0.5 - 1/2 */
if currentOffset > contentHeight * 0.75 {
offset = currentOffset - contentHeight * 0.75
// reload content here
scrollView.contentOffset = CGPoint(x: 0, y: currentOffset - contentHeight * 0.5 - offset + CGFloat(cellHeight))
}
It works very nice, but need to play with contentOffset y formula because right now trick works only when you scroll up. Will fix this tomorrow and add calendar date calculations.
EDIT 2
reloading data ruins everything in all of my prototypes. Including the one I made previously. Found something that makes lags a bit lower but still very noticeable and totally unacceptable. Here are these things:
remove autolayout from cell prototype in the storyboard
add this code to the cell:
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
I think that invalidation of the layout results in these lags. So, I maybe I need to make custom flow layout? And if so what recommendations can you give.
That's actually the very interesting topic. I'll try to explain the idea I came up with while working on another Calendar app.
In my case it was not a Calendar View but Day View, hours 00:00 - 24:00 listed from top to bottom, so I had UITableView and not UICollectionView but it's not that important in this case.
The language used was not Swift but Objective-C, so I just try to translate code samples.
Instead of manipulation with the data source I created UITableView with the fixed amount of rows, in my case to store exactly two days (48 rows, two days of 24 hours each). You could choose the amount of rows containing in two full screens.
The important thing is that total amount of rows must be a multiple of 8.
Then you need a formula to calculate what's the day number for each particular cell based on what's inside the first visible row.
The idea is that UICollectionView is in fact UIScrollView so when we scroll down or up we can handle the corresponding event and calculate the visible offsets.
To simulate the infinitive scrolling we handle the scrollViewDidScroll and check if you just passed the 1/8 of the UIScrollView height scrolling up, move your UIScrollView to the 1/2 of height plus the exact offset so it moves to the "second screen" smoothly. And back, if you passed the 6/8 of the UIScrollView height while scrolling down, move the UIScrollView up to 1/2 of its height minus offset.
When you do this you see scrolling indicator jumps up and down which is very confusing so we have to hide it, just put somewhere in viewDidLoad:
calendarView.showsHorizontalScrollIndicator = false
calendarView.showsVerticalScrollIndicator = false
where calendarView is your UICollectionView instance name.
Here is the code (translated from Objective-C right here, not tried in the real project):
func scrollViewDidScroll(scrollView_:UIScrollView) {
if !enableScrollingHandling {return}
var currentOffsetX:CGFloat = scrollView_.contentOffset.x
var currentOffSetY:CGFloat = scrollView_.contentOffset.y
var contentHeight:CGFloat = scrollView_.contentSize.height
var offset:CGFloat
/* 0.125 - 1/8, 0.5 - 1/2 */
if currentOffSetY < (contentHeight * 0.125) {
enableScrollingHandling = false
offset = (contentHeight * 0.125) - currentOffSetY
// #todo: your code, specify which days are listed in the first row (2nd screen)
scrollView_.contentOffset = CGPoint(x: currentOffsetX, y: currentOffset +
contentHeight * 0.5 + offset - CGFloat(kRowHeight))
enableScrollingHandling = true
return
}
/* 0.75 - 6/8, 0.5 - 1/2 */
if currentOffSetY > (contentHeight * 0.75) {
enableScrollingHandling = false
offset = currentOffSetY - (contentHeight * 0.75)
// #todo: your code, specify which days are listed in the first row (1st screen)
scrollView_.contentOffset = CGPoint(x: currentOffsetX, y: currentOffset -
contentHeight * 0.5 - offset + CGFloat(kRowHeight))
enableScrollingHandling = true
return
}
}
Then in your collectionView:cellForItemAtIndexPath: based on what's in the first row (is this content of the first screen or second screen) and what are the current visible days (formula) you just update the cell content.
This formula could also be a tricky part and may require some workaround especially if you decide to put Month Names in between months. I do also have some ideas on how to organise it, so if you encounter any problem we can discuss it here.
But such an approach to simulate infinitive scrolling with "two screens loop and jumps in between" works really like a charm, very smooth scrolling like behaviour tested on older phones also.
PS: kRowHeight is just a constant the height of the exact row (cell) it's needed for precise and smooth scrolling behaviour it could be skipped I think.
UPDATE:
Important notice, I skipped this from original code, but see this is important. When you manually set the contentOffset you also triggers the scrollViewDidScroll event, to prevent this you need to temporary disable your scrollViewDidScroll event processing. You can do it by adding, for example, state variable enableScrollHandling and change its state true/false, see updated code.
If you need more details: I have UICollectionViewCell which is draggable. When the cell overlaps other cells during dragging I want to know which one it overlaps mostly in order to replace overlapped cell with this cell. Is there any efficient way to do this? I found nothing helpful in CGGeometry. Thank you.
You can use CGRectUnion() in a loop, the one with a smallest area will be the one with the greatest overlap. You could write a function that deals with (and returns a) CGRects, but youd probably need to loop though your views (cells) again to find the correct one, so I'd keep it UIView level...
Eg
//helpers
CGFloat CGRectGetArea(CGRect theRect){
return theRect.size.width * theRect.size.height;
}
-(UIView *)viewClosestToView:(UIView*)keyView fromViews:(NSArray*)comparisonViews{
UIView *result=nil;
CGFloat smallestArea=0.0;
for (UIView *view in comparisonViews){
CGFloat area = CGRectGetArea( CGRectUnion(view.frame, keyView.frame) );
if ((area < smallestArea)|| ((NSInteger)smallestArea==0) ){
smallestArea=area;
result=view;
}
}
return result;
}
Use CGRectIntersection(r1,r2) to find the intersection rect (if any). Multiply width and height of the returned rectangle to get the area. Compare the 4 areas to find the largest.
This should be way more than fast enough for UI manipulation.
I want to know if it is possible to have custom spacing between bars after some fixed interval using Coreplot iOS library.
Like in the image below, after each 7 bars an unusual barspace is shown.
And if it is possible can you please guide how can this be achieved ?
CPTBarPlot has the code to manage this.
-(BOOL)barAtRecordIndex:(NSUInteger)idx basePoint:(CGPoint *)basePoint tipPoint:(CGPoint *)tipPoint
Basically gets the bar and sets its basePoint and tipPoint.
At the end, it is using barOffsetLength to offset each bar based on its index.
CGFloat barOffsetLength = [self lengthInView:self.barOffset] * self.barOffsetScale;
For vertical bars, in your case, its offsetting the x coord of base and tip point. These are usually the same. Here you have the choice of adding your own offset.
Simply, here's what you need to do there in the same function:
CGFloat barOffsetLength = [self lengthInView:self.barOffset] * self.barOffsetScale;
if ([self.dataSource hasGapBeforeIndex:idx]) {
offsetGap += [self.dataSource gapValue];
}
// Offset
if ( horizontalBars ) {
basePoint->y += barOffsetLength;
tipPoint->y += barOffsetLength;
}
else {
//HERO
basePoint->x += barOffsetLength + offsetGap;
tipPoint->x += barOffsetLength + offsetGap;
}
Here, you introduce a new variable in CPTBarPlot called offsetGap which gets increments everytime you introduce a gap. (be careful, this needs to be reset to zero when you change the dataset).
Also, in CPTPlotDataSource introduce
- (BOOL) hasGapBeforeIndex:(NSUInteger)index;
- (CGFloat) gapValue;
and implement it in your View Controller. Now you can introduce the gap anywhere.
PS: This obviously is a hack and upsets the axis labels and other things that might also need adjustment, but gives an overview anyway.
I played around with the sample app to achieve this.
You need to modify the positioning in your Core Plot data source method for the x axis
- (NSNumber *) numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx
and take into account where you want the spacing to occur. If you still don't get it, please post some code and I'll show you on that.
Logic example :
I want to represent the data for a month, lets say one that has 30 days, but at each 5 days, I want a pause at each 5 days. So instead of returning 30 in
- (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
, you return 34, and at indexes 6, 11, 16, 21 and 26 you return 0 for the method above.
You can extend this if you want not that much space for the 'pauses' and return double the amount of days (60), minus 4 (because for the pauses you return only for one record the value 0) and return for each 2 records the corresponding value in your data source. This can be again extended to your needed multiplier. I hope you got what I mean.
Thanks to #zakishaheen answer I managed to achieve this, but I broke label position and scroll content size 😄. This implementation is hacky thats why I decided not to continue with fixing it, its more just an example.
I created custom CustomOffsetBarPlot class and apply some Objective-C runtime magic.
- (BOOL)superImplementation:(SEL)selector idx:(NSUInteger)idx basePoint:(nonnull CGPoint *)basePoint tipPoint:(nonnull CGPoint *)tipPoint {
Class granny = [self superclass];
BOOL(* grannyImp)(id,SEL,NSUInteger,CGPoint*, CGPoint*) = (BOOL (*)(id,SEL,NSUInteger,CGPoint*, CGPoint*))class_getMethodImplementation(granny, selector);
return grannyImp(self, selector, idx, basePoint, tipPoint);
}
-(BOOL)barAtRecordIndex:(NSUInteger)idx basePoint:(nonnull CGPoint *)basePoint tipPoint:(nonnull CGPoint *)tipPoint {
SEL selector = _cmd;
CGPoint originBasePointStart = *basePoint;
CGPoint originTipPointStart = *tipPoint;
[self superImplementation:selector idx:0 basePoint:&originBasePointStart tipPoint:&originTipPointStart];
BOOL result = [self superImplementation:selector idx:idx basePoint:basePoint tipPoint:tipPoint];
Class granny = [self class];
SEL lengthView = NSSelectorFromString(#"lengthInView:");
CGFloat(* grannyImp)(id,SEL,NSDecimal) = (CGFloat (*)(id,SEL,NSDecimal))class_getMethodImplementation(granny, lengthView);
CGFloat barOffsetLengthOrigin = grannyImp(self, selector, self.barOffset.decimalValue);
NSInteger barOffsetLength = originBasePointStart.x + idx * 18 + idx * 5; // idx * 5 - your offset
basePoint->x = barOffsetLength;
tipPoint->x = barOffsetLength;
return result;
}
I've been checking the Tapku Calendar code for a bit and searched and read all the relevant questions and responses here however none seem to really offer the correct solution to the problem: How to select multiple dates, either programmatically or by tapping. Just a simple blue tile over two adjacent dates would make me happy :-) The post below seems to have a similar question however the answer does not work. The place in the code is not hit unless the month changes - not exactly what I am looking for. What would be great is a higher-level implementation of selectDate: that would select multiple dates. But just the right place to tweak in the library would be a great place to start is anyone is more familiar with the code. Much appreciated.
iOS: Tapku calendar library - allow selecting multiple dates for current month
So after a bit of stepping through code, I have this rudimentary method using a hammer. I adopted most of the code from TKCalendarMonthView.m->selectDay:day method. The method I created basically creates a new TKCalendarMonthTiles object and fills in the details and then adds subviews onto the main TKCalendarMonthTiles object (self). I tag the subviews so I can first get rid of them if they exist at the beginning of the method as I only want to select one additional day (you could leave the subviews attached if you want them to remain in the UI). I don't track the dates or store them or anything however this meets my needs.
The idea is to simply create a view with the correct tile image you want to use and one that contains the text label of the actual "date" like "14" then add those views as subviews to self. The borrowed code does all the calculations for "where" that date tile resides in the grid, so the view is drawn at the correct location. Code:
- (void)markDay:(int)day {
// First, remove any old subviews
[[self viewWithTag:42] removeFromSuperview];
[[self viewWithTag:43] removeFromSuperview];
int pre = firstOfPrev < 0 ? 0 : lastOfPrev - firstOfPrev + 1;
int tot = day + pre;
int row = tot / 7;
int column = (tot % 7)-1;
TKCalendarMonthTiles *deliveryTile = [[TKCalendarMonthTiles alloc] init];
deliveryTile.selectedImageView.image = [UIImage imageWithContentsOfFile:TKBUNDLE(#"TapkuLibrary.bundle/Images/calendar/MyDateTile.png")];
deliveryTile.currentDay.text = [NSString stringWithFormat:#"%d",day];
if(column < 0){
column = 6;
row--;
}
CGRect r = deliveryTile.selectedImageView.frame;
r.origin.x = (column*46);
r.origin.y = (row*44)-1;
deliveryTile.selectedImageView.frame = r;
deliveryTile.currentDay.frame = r;
[[deliveryTile selectedImageView] setTag:42];
[[deliveryTile currentDay] setTag:43];
[self addSubview:deliveryTile.selectedImageView];
[self addSubview:deliveryTile.currentDay];
} // markDay:
I call this method at the end of TKCalendarMonthView.m->selectDay:day as well as at the end of TKCalendarMonthView.m->-reactToTouch:down. Limited testing so far so good. Off to figure out why the timezone setting keeps thinking its tomorrow (I am in Pacific time zone).
Cheers, Michael