I'm making a calendar and the year view in particular, and understandably, is quite slow when coming into view.
The year view is simply a UICollectionView, and each month is a custom UICollectionViewCell XIB. The month has a title UILabel, and 7 dayName UILabels, and 42 dayNumber UILabels.
That's a total of 50 UILabels per month X 12. Aieesh...
How do other iOS calendar apps handle showing so many subviews without degradation in performance? The performance hit is mainly when swiping this view to the next and previous years. As the view is sliding in, the collection view is propagating the cells, causing the slow-down. I could hold off on populating the cells until the scrolling has stopped, but I'd really like to not show a white view as it's scrolling into the window.
Edit* As requested - my cellForRow method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
NSIndexPath *indexPathPlusOne = [NSIndexPath indexPathForRow:indexPath.row + 1 inSection:indexPath.section];
EICalendarYearViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"EICalendarYearViewCell" forIndexPath:indexPath];
if (_datesLoaded == YES)
{
//Get First Day in Month
NSMutableArray *arrayForDatesInMonth = [_dictHoldingArrayForEachMonth objectForKey:[NSNumber numberWithLong:indexPathPlusOne.row]];
NSDate *firstDayInMonth = arrayForDatesInMonth[0];
NSDateComponents* comp = [[NSCalendar currentCalendar] components:(NSCalendarUnitWeekday) fromDate:firstDayInMonth];
//setting month label
switch (indexPathPlusOne.row) {
case 1:
cell.labelTitle.text = #"January";
break;
case 2:
cell.labelTitle.text = #"February";
break;
case 3:
cell.labelTitle.text = #"March";
break;
case 4:
cell.labelTitle.text = #"April";
break;
case 5:
cell.labelTitle.text = #"May";
break;
case 6:
cell.labelTitle.text = #"June";
break;
case 7:
cell.labelTitle.text = #"July";
break;
case 8:
cell.labelTitle.text = #"August";
break;
case 9:
cell.labelTitle.text = #"September";
break;
case 10:
cell.labelTitle.text = #"October";
break;
case 11:
cell.labelTitle.text = #"November";
break;
case 12:
cell.labelTitle.text = #"December";
break;
default:
break;
}
//setting number of days in previous month to show.
long previousMonthDays = 0;
if (comp.weekday == 7)
{
previousMonthDays = 0;
}
else
{
previousMonthDays = comp.weekday - 1;
}
for (int i = 0; i < cell.arrayOfDayLabels.count; i++)
{
UILabel *labelDay = cell.arrayOfDayLabels[i];
//previous Month days
if (i <= previousMonthDays)
{
labelDay.text = #" ";
labelDay.textColor = [UIColor colorWithWhite:0.8 alpha:1.000];
}
//Actual days in current month
if (i >= previousMonthDays && i < previousMonthDays + arrayForDatesInMonth.count)
{
NSDate *dateForDay = arrayForDatesInMonth[i - previousMonthDays];
NSDateComponents *compsForDay = [[NSCalendar currentCalendar] components:NSCalendarUnitDay fromDate:dateForDay];
labelDay.text = [NSString stringWithFormat:#"%ld", (long)compsForDay.day];
labelDay.textColor = [UIColor darkGrayColor];
}
else
{
labelDay.text = #" ";
labelDay.textColor = [UIColor colorWithWhite:0.8 alpha:1.000];
}
}
}
else
{
for (int i = 0; i < cell.arrayOfDayLabels.count; i++)
{
UILabel *labelDay = cell.arrayOfDayLabels[i];
labelDay.text = #" ";
}
}
cell.layer.shouldRasterize = YES;
[cell.layer setRasterizationScale:3.0f];
return cell;
}
For rmaddy, a screenshot of the methods causing majority of slow-down.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
I have four squares on my screen and I want each of the squares to display a different image every time for each square. I have got it to work, but as the code repeats sometimes there is a delay. I'm not sure what I'm doing wrong.
Here is my main code
#import "HomeScreenViewController.h"
#interface HomeScreenViewController ()
#end
#implementation HomeScreenViewController
-(void)startRandomSquares
{
self.greenImage = [UIImage imageNamed:#"GreenSquare.png"];
self.redImage = [UIImage imageNamed:#"RedSquare"];
self.blueImage = [UIImage imageNamed:#"BlueSquare"];
self.purpleImage = [UIImage imageNamed:#"PurpleSquare"];
self.arrayOfColors = [NSMutableArray arrayWithObjects:self.greenImage,self.redImage, self.blueImage, self.purpleImage, nil];
}
-(IBAction)Button1:(id)sender
{
self.startLabel.hidden = YES;
}
-(IBAction)Button2:(id)sender
{
}
-(IBAction)Button3:(id)sender
{
}
-(IBAction)Button4:(id)sender
{
}
-(void)randomizeAllSquares
{
[self randomizedSquare1];
[self randomizedSquare2];
[self randomizedSquare3];
[self randomizedSquare4];
}
-(void)randomizedSquare1
{
[self startRandomSquares];
randomColor = arc4random() % [self.arrayOfColors count];
square1RandomNumber = randomColor;
// NSLog(#"Square 1 : %d", square1RandomNumber);
//NSLog(#"Square 1 colors : %#", self.arrayOfColors);
switch (square1RandomNumber) {
case 0:
self.square1.image = self.arrayOfColors[0];
break;
case 1:
self.square1.image = self.arrayOfColors[1];
break;
case 2:
self.square1.image = self.arrayOfColors[2];
break;
case 3:
self.square1.image = self.arrayOfColors[3];
break;
default:
break;
}
if (square1RandomNumber == square2RandomNumber ) {
[self randomizedSquare1];
NSLog(#"Square 1-2 MATCH!!!");
}
if (square1RandomNumber == square4RandomNumber) {
[self randomizedSquare1];
NSLog(#"Square 1-4 Match!!!");
}
}
-(void)randomizedSquare2
{
[self startRandomSquares];
randomColor = arc4random() % [self.arrayOfColors count];
square2RandomNumber = randomColor;
//NSLog(#"Square 2 : %d", square2RandomNumber);
//NSLog(#"Square 2 colors : %#", self.arrayOfColors);
switch (square2RandomNumber) {
case 0:
self.square2.image = self.arrayOfColors[0];
break;
case 1:
self.square2.image = self.arrayOfColors[1];
break;
case 2:
self.square2.image = self.arrayOfColors[2];
break;
case 3:
self.square2.image = self.arrayOfColors[3];
break;
default:
break;
}
if (square2RandomNumber == square1RandomNumber ) {
[self randomizedSquare2];
NSLog(#"Square 2-1 Match!!!");
}
if (square2RandomNumber == square4RandomNumber) {
[self randomizedSquare2];
NSLog(#"Square 2-4 Match!!!");
}
}
-(void)randomizedSquare3
{
[self startRandomSquares];
randomColor = arc4random() % [self.arrayOfColors count];
square3RandomNumber = randomColor;
//NSLog(#"Square 3 : %d", square3RandomNumber);
//NSLog(#"Square 3 colors : %#", self.arrayOfColors);
switch (square3RandomNumber) {
case 0:
self.square3.image = self.arrayOfColors[0];
break;
case 1:
self.square3.image = self.arrayOfColors[1];
break;
case 2:
self.square3.image = self.arrayOfColors[2];
break;
case 3:
self.square3.image = self.arrayOfColors[3];
break;
default:
break;
}
if (square3RandomNumber == square2RandomNumber ){
[self randomizedSquare3];
NSLog(#"Square 3 - 2 MATCH!!!");
}
if (square3RandomNumber == square1RandomNumber) {
[self randomizedSquare3];
NSLog(#"Square 3- 1 MATCH!!");
}
}
-(void)randomizedSquare4
{
[self startRandomSquares];
randomColor = arc4random() % [self.arrayOfColors count];
square4RandomNumber = randomColor;
//NSLog(#"Square 4 : %d", square4RandomNumber);
//NSLog(#"Square 4 colors : %#", self.arrayOfColors);
switch (square4RandomNumber) {
case 0:
self.square4.image = self.arrayOfColors[0];
break;
case 1:
self.square4.image = self.arrayOfColors[1];
break;
case 2:
self.square4.image = self.arrayOfColors[2];
break;
case 3:
self.square4.image = self.arrayOfColors[3];
break;
default:
break;
}
if (square4RandomNumber == square2RandomNumber ) {
[self randomizedSquare4];
NSLog(#"Square 4-2 MATCH!!");
}
if (square4RandomNumber == square1RandomNumber) {
[self randomizedSquare4];
NSLog(#"SQuare 4-1 MATCH!!");
}
if (square4RandomNumber == square3RandomNumber) {
[self randomizedSquare4];
NSLog(#"Square 4-3 Match");
}
}
Ouch, that's a lot of code duplication. In general if you find yourself writing a similar piece of code multiple times it's not just best practice to generalize it, but you'll likely end up with a better solution.
A simple shuffle function on your array along with a simple reassign would work, and will give a runtime of N (ie the loop will only go through the array once per call). What you have now will technically try to create the color for a square multiple times in the case that it finds a match which is the reason for your slowness. Square 4 has a 3/4 chance that it will get the same color as another square so it likely runs at least 3 times before stopping.
- (void)shuffleImages
{
//uses the Fisher-Yates shuffle algorithm
for (NSUInteger i = self.arrayOfColors.count - 1; i >= 1; i--)
{
int index = arc4random_uniform(i+1);
[self.arrayOfColors exchangeObjectAtIndex:index withObjectAtIndex:i];
}
}
- (void)updateImages
{
[self shuffleImages];
self.square1.image = self.arrayOfColors[0];
self.square2.image = self.arrayOfColors[1];
self.square3.image = self.arrayOfColors[2];
self.square4.image = self.arrayOfColors[3];
}
All you need to do when you want your colors to change is call the updateImages function.
The delay comes from the check at the end of each randomisation. There's a 75% chance the number generated is in one of the other 3 squares, so it will loop and loop until it generates the only number it can be.
A better solution would be to shuffle the array of images, and then assign each image to each view in order.
e.g. (pseudo-code)
shuffle array of images
view 1 = array[0]
view 2 = array[1]
...
view 4 = array[3]
Brandon is right about the insane amount of code duplication, and using the fischer-yates algorithm is definitely the right way to go, but it sounds like you also want this to repeat, so you should use an NSTimer.
#property (strong, nonatomic) NSTimer *randomizeColorTimer;
- (void)startRandomizing
{
_randomizeColorTimer = [NSTimer scheduledTimerWithTimeInterval:0.25
target:self
selector:#selector(shuffleImages)
userInfo:nil
repeats:YES];
}
- (void)stopRandomizing
{
[_randomizeColorTimer invalidate];
_randomizeColorTimer = nil;
}
array of images added into Project
imaging is the UIImageView and imagg is Image choosen by user its default
_imaging.image=imagg;
arr = [NSArray arrayWithObjects:
[UIImage imageNamed:#"1.jpg"],
[UIImage imageNamed:#"2.jpeg"],
[UIImage imageNamed:#"3.jpeg"],
[UIImage imageNamed:#"4.jpeg"],
[UIImage imageNamed:#"5.jpeg"],
nil];
assume user selected third image i.e 3.jpeg i am showing the 3.jpeg in imageview. Then using two button actions respectively Next and Previous next or previous images 3.jpeg should display.
- (IBAction)previous:(id)sender {
////
}
- (IBAction)next:(id)sender {
//////
}
}
for the next action its starting from 2nd image even for previous action its starting from 1st image.
this is the simple concept, this is working in static method, but working fine
assign the one global variable
#property (strong,nonatomic) NSString *imagestr;
- (IBAction)previous:(id)sender {
int str=[imagestr integerValue];
NSLog(#"the int value==%d",str);
switch (str) {
case 0:
_imaging.image=[arr objectAtIndex:4];
imagestr=#"4";
break;
case 1:
_imaging.image=[arr objectAtIndex:0];
imagestr=#"0";
break;
case 2:
_imaging.image=[arr objectAtIndex:1];
imagestr=#"1";
break;
case 3:
_imaging.image=[arr objectAtIndex:2];
imagestr=#"2";
break;
case 4:
_imaging.image=[arr objectAtIndex:3];
imagestr=#"3";
break;
default:
break;
}
}
- (IBAction)next:(id)sender {
int str=[imagestr integerValue];
NSLog(#"the int value==%d",str);
switch (str) {
case 0:
_imaging.image=[arr objectAtIndex:1];
imagestr=#"1";
break;
case 1:
_imaging.image=[arr objectAtIndex:2];
imagestr=#"2";
break;
case 2:
_imaging.image=[arr objectAtIndex:3];
imagestr=#"3";
break;
case 3:
_imaging.image=[arr objectAtIndex:4];
imagestr=#"4";
break;
case 4:
_imaging.image=[arr objectAtIndex:0];
imagestr=#"0";
break;
default:
break;
}
}
On clicked on next button
Before using this first set currentIndex in this
-(IBAction)next{
if (arr.count > 0) {
currentIndex++;
if (currentIndex > arr.count - 1) {
currentIndex = 0;
}
imageViewObj.image = [arr objectAtIndex:currentIndex];
}
}
on pervious button
-(IBAction)pervious{
if (arr.count > 0) {
currentIndex--;
if (currentIndex <= 0) {
currentIndex = arr.count - 1;
}
imageViewObj.image = [arr objectAtIndex:currentIndex];
}
}
Try this is working fine
Set some integer property currentIndex to 2 (if you load first the 3rd image).
When calling the next/previous methods increment/decrement that property and load the correct image. ImageView *image = [arr objectAtIndex:currentIndex]
when you initialize your view controller, keep track of the current image being viewed for example 0, to be used as an array index.
When next is pressed, increment the index and retrieve the image from the array using the updated count.
e.g.
- (IBAction)next:(id)sender {
imageCount++;
[myImageView setImage:[UIImage imageNamed:arr[imageCount] ];
}
hope this helps. :)
I have a UICollectionView that uses drag and drop functionality to perform manual re-order of the cells.
I only have 1 section that contains two columns of equally sized cells.
To explain my problem properly I would need to upload screenshots but I don't have enough rep, so I will have to explain using numbers.
1 2 3 4 5 6
This represents a CollectionView with 6 cells in 2 columns filling the entire screen.
So the problem i'm encountering is that if I swap, for example, cell 3 and cell 2 everything works correctly because cell 3 becomes cell 2 and vice versa, the issue happens when I swap cell 3 and cell 1 because then cell 3 becomes cell1 and cell1 becomes cell2 and cell2 becomes cell3.
Now it re-arranges but as soon as the app calls [self.collectionView reloadData]; cell3 will stay at 1 where I dropped it, but the new cell 2 and 3 will swap positions as if there indexes havn't updated.
A long press gesture handles the remove and insert of the cell(snapshot of cell) when dragged and all the tiles re-arrange and fit properly into the sections.
-(IBAction)longPressGestureRecognized:(id)sender {
UILongPressGestureRecognizer *longPress = (UILongPressGestureRecognizer *)sender;
UIGestureRecognizerState state = longPress.state;
CGPoint location = [longPress locationInView:self.collectionView];
NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location];
static UIView *snapshot = nil;
static NSIndexPath *sourceIndexPath = nil;
switch (state) {
case UIGestureRecognizerStateBegan: {
if (indexPath) {
sourceIndexPath = indexPath;
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
snapshot = [self customSnapshotFromView:(cell)];
__block CGPoint center = cell.center;
snapshot.center = center;
snapshot.alpha = 0.98;
[self.collectionView addSubview:snapshot];
[UIView animateWithDuration:0.25 animations:^{
center.y = location.y;
snapshot.center = center;
snapshot.transform = CGAffineTransformMakeScale(1.05, 1.05);
snapshot.alpha = 0.98;
cell.hidden = YES;
} completion:nil];
}
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint center = snapshot.center;
center.y = location.y;
center.x = location.x;
snapshot.center = center;
if (indexPath && ![indexPath isEqual:sourceIndexPath]) {
[people2 exchangeObjectAtIndex:indexPath.item withObjectAtIndex:sourceIndexPath.item];
[self.collectionView moveItemAtIndexPath:sourceIndexPath toIndexPath:indexPath];
sourceIndexPath = indexPath;
[databaseClass arrangeData:people2];
}
break;
}
default: {
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:sourceIndexPath];
[UIView animateWithDuration:0.25 animations:^{
snapshot.center = cell.center;
snapshot.transform = CGAffineTransformIdentity;
snapshot.alpha = 0.0;
cell.hidden = NO;
} completion:^(BOOL finished){
[snapshot removeFromSuperview];
snapshot = nil;
}];
sourceIndexPath = nil;
break;
}
}
}
I am using a sqlite method to store the arrangement of the tiles in the database.
+(void)arrangeData:(NSArray*)arrange {
[self databaseInit];
if (sqlite3_open(dbpath, &peopleDB ) == SQLITE_OK)
{
for (int i = 0; i < [arrange count]; i++) {
NSString *insertSQL = [NSString stringWithFormat:#"UPDATE PEOPLE SET ARRANGE = %i WHERE ID = %i",i+1,[[arrange objectAtIndex:i] ID]];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2( peopleDB, insert_stmt,-1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
{
NSLog(#"Arranged Successfully");
}
else {
NSLog(#"%s SQLITE_ERROR '%s' (%1d)", __FUNCTION__, sqlite3_errmsg(peopleDB), sqlite3_errcode(peopleDB));
}
sqlite3_finalize(statement);
}
sqlite3_close(peopleDB);
}
Not that it matters much but the snapshot :
-(UIView *)customSnapshotFromView:(UIView *)inputView {
UIView *snapshot = [inputView snapshotViewAfterScreenUpdates:YES];
snapshot.layer.masksToBounds = NO;
snapshot.layer.cornerRadius = 0.0;
snapshot.layer.shadowOffset = CGSizeMake(-5.0, 0.0);
snapshot.layer.shadowRadius = 5.0;
snapshot.layer.shadowOpacity = 0.4;
return snapshot;
}
If anyone has any ideas on how to get the cells to stay where they are when the longpress ends I would be hugely appreciative.
I don't understand sqlite, but according to what you say, it seems that you swap data instead of moving the concerned data.
In your example you have to delete dataForCell3 from it's array and then insert it at the index of dataForCell1.
Seems that instead of that you swapped dataForCell3 and dataForCell1.
I'm new to Xcode and i'm really stuck.
I have a UIProgressView which fills up with a UISwipeGestureRecognizer. I also have a switch statement. Now I want that only 7 ways to run (from the 242) while progressTrue is false. If progressTrue is true, so the UIProgressView is on 1.0, it should select new 7 cases randomly. Is something like that possible?
Thanks for any help!
-(void)vorgangDev {
int number = arc4random() % 242;
switch (number) {
case 0:
question.text = #"Anlieferung/anliefern";
answer.text = #"delivery, supply/deliver, supply";
answer.hidden = YES;
break;
case 1:
question.text = #"Artikel";
answer.text = #"article, item";
answer.hidden = YES;
break;
case 2:
question.text = #"Artikelanzahl";
answer.text = #"number of articles/ ~items";
answer.hidden = YES;
break;
//And so on...
}
Here I call vorgangDev.
if (progressTrue.progress == 1.0) {
// The UIProgressView is full
// if this is true do the question.
CABasicAnimation* fade = [CABasicAnimation animationWithKeyPath:#"backgroundColor"];
fade.fromValue = (id)[UIColor blackColor].CGColor;
fade.toValue = (id)[UIColor colorWithRed:50/255.0f green:109/255.0f blue:43/255.0f alpha:1.0f].CGColor;
[fade setDuration:1];
[self.view.layer addAnimation:fade forKey:#"fadeAnimation"];
progressTrue.progress = 0.0;
[self vorgangDev];
There are two sections in my table view, one for test status and one for results.
The third row in the first section gets changed upon the test completing. During the test it documents the percentage complete and after it changes to showing if any problems have been detected.
The color of the text also changes to indicate bad/ok/good. For this particular test there are about 20 rows of results.
The problem being that within those 20 results two cells are being treated like the third cell in section one and turning red.
Now I assume as I scroll the table reloads which must mean my code is incorrect and has only be shown up by a test with lots of results. Any help would be great. I suspect it is the code below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
{
UITableViewCell *serverLoc = [tableView dequeueReusableCellWithIdentifier:#"speedCell"];
// UITableViewCell *switchCell = [tableView dequeueReusableCellWithIdentifier:#"switchCell"];
switch (indexPath.section) {
case 0:
switch (indexPath.row) {
case 0:
serverLoc.textLabel.text = #"Test location:";
serverLoc.detailTextLabel.text = testLocation;
serverLoc.selectionStyle = UITableViewCellSelectionStyleNone;
serverLoc.userInteractionEnabled = NO;
break;
case 1:
serverLoc.textLabel.text = #"Status:";
serverLoc.detailTextLabel.text = statusText;
serverLoc.selectionStyle = UITableViewCellSelectionStyleNone;
serverLoc.userInteractionEnabled = NO;
break;
case 2:
if ([TestEnded isEqualToString:#"no"]) {
serverLoc.textLabel.text = #"Progress";
serverLoc.selectionStyle = UITableViewCellSelectionStyleNone;
serverLoc.userInteractionEnabled = NO;
serverLoc.detailTextLabel.text = [NSString stringWithFormat:#"%ld%%", (long)progressInt];
break;
}
else {
serverLoc.selectionStyle = UITableViewCellSelectionStyleBlue;
serverLoc.userInteractionEnabled = YES;
serverLoc.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
serverLoc.textLabel.text = #"Problems Detected:";
serverLoc.detailTextLabel.text = [NSString stringWithFormat:#"%ld", (long)problemsDetected];
if (problemsDetected == 0) {
serverLoc.textLabel.textColor = [UIColor colorWithRed:0.0 / 255 green:102.0 / 255 blue:51.0 / 255 alpha:1.0];
serverLoc.detailTextLabel.textColor = [UIColor colorWithRed:0.0 / 255 green:102.0 / 255 blue:51.0 / 255 alpha:1.0];
}
else if (problemsDetected == 1) {
serverLoc.textLabel.textColor = [UIColor colorWithRed:226.0 / 255 green:232.0 / 255 blue:52.0 / 255 alpha:1.0];
serverLoc.detailTextLabel.textColor = [UIColor colorWithRed:226.0 / 255 green:232.0 / 255 blue:52.0 / 255 alpha:1.0];
}
else {
serverLoc.textLabel.textColor = [UIColor redColor];
serverLoc.detailTextLabel.textColor = [UIColor redColor];
}
}
break;
}
break;
I wasn't sure if it would work when I first implemented it. It "did" but clearly I wasn't needing to scroll enough to unveil the bug.
Thanks in advance for any pointers
The code here looks fine... It sounds like your table is reusing the cell set up for this particular path but not returning the values to default before reusing the cell. (e.g. in the section == 1 case: severLoc.textLabel.textColor = [UIColor blackColor];) This will also show up if case 0,0 and 0,1 end up with a 0,2 cell when grabbing a reused cell.