简体   繁体   English

UIView animateWithDuration在iOS 8中的行为有所不同

[英]UIView animateWithDuration behaves differently in iOS 8

I just switched from iOS 7.1 to iOS 8.1. 我刚刚从iOS 7.1切换到iOS 8.1。 am encountering the following problem with UIView animateWithDuration . UIView animateWithDuration遇到以下问题。 Pictures being worth a thousand words, I made videos of the behavior under each iOS: 图片价值一千个字,我制作了每个iOS下行为的视频:

iOS 7.1: iOS 7.1:

https://vimeo.com/113887812 https://vimeo.com/113887812

iOS 8.1: iOS 8.1:

https://vimeo.com/113887814 https://vimeo.com/113887814

It's an iPhone app. 这是一个iPhone应用程序。

I should note that I've searched SO and other places before posting. 我应该注意,发布之前我已经搜索过SO和其他地方。 It seems to be a recognized problem, but what I've found are a few references to "using constraint-based animation" (which I didn't understand) and turning off Auto layout, but no real consensus. 这似乎是一个公认的问题,但是我发现一些引用是“使用基于约束的动画”(我不理解),并且关闭了“自动”布局,但没有真正的共识。

I've tried a couple of stabs, but have gotten nowhere. 我试过几次刺,但是一无所获。 If someone can point me to (or walk me through) an explicit fix, I'd really appreciate it. 如果有人可以指出(或引导我)进行明确的修复,我将不胜感激。

I should mention that the app is intended to work only in the portrait position, so I gave no thought to auto layout during the development process. 我应该提到该应用程序只能在纵向模式下使用,因此在开发过程中我没有考虑自动布局。

All the views involved are created programmatically, except the scroller itself. 除了滚动器本身以外,所有涉及的视图都是以编程方式创建的。

Here is the code. 这是代码。 I included the whole method, which is pretty long, but I really don't understand what has changed in the new iOS, so am being on the safe side. 我包括了整个方法,这相当长,但是我真的不明白新iOS中发生了什么变化,因此处于安全起见。 The animateWithDuration call is pretty deep into the chunk: animateWithDuration调用相当深入:


-(void) displayDayChart
{
    [self.scroller setBackgroundColor:[UIColor clearColor]];

    UIImage *image = [UIImage imageNamed:@"GrayPaper.png"];
    self.chartView.backgroundColor = [UIColor colorWithPatternImage:image];

    [self.view addSubview:self.scroller];
    [self.scroller setScrollEnabled:YES];

    [self.scroller setContentSize:CGSizeMake(320, 1800)];
    [self.scroller setContentOffset:CGPointMake(0.0, 0.0) animated:YES];

    dayBarArray = [[NSMutableArray alloc]init];
    infoLabelArray = [[NSMutableArray alloc]init];
    timeLabelArray = [[NSMutableArray alloc]init];

    timeFrameDC = - 86400; // 24 hours for this test, selectable in real life

    [self.scroller.subviews makeObjectsPerformSelector: @selector(removeFromSuperview)];

    timeframeDCStart = [NSDate dateWithTimeIntervalSinceNow:timeFrameDC];
    timeframeDCEnd = [timeframeDCStart dateByAddingTimeInterval:abs(timeFrameDC)];

    actPredicate = [NSPredicate predicateWithFormat:@"(startTime >= %@ AND stopTime <= %@) OR ((startTime <= %@ AND stopTime >= %@) OR (startTime <= %@ AND stopTime == NULL))",timeframeDCStart,timeframeDCEnd,timeframeDCStart,timeframeDCStart,timeframeDCEnd];

    NSFetchedResultsController *dayActivityFRC = [TimedActivity MR_fetchAllSortedBy:@"startTime" ascending:YES withPredicate:actPredicate groupBy:nil delegate:nil];


    double taDuration;
    double activityPercent;
    int barHeight;
    int lastHeight = 0;
    int tickerCount = 1;
    int barY;
    int lastBarY = 0;
    CGRect lastBarFrame;



#pragma mark DisplayDayChart setup

    for (TimedActivity *activity in dayActivityFRC.fetchedObjects)
    {
        // Create bar and associated labels

        thisBar = [[DayChartBar alloc] initWithFrame:CGRectZero];

        thisInfoLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        thisTimeLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        arrowView = [[UIView alloc]initWithFrame:CGRectZero];

        thisBar.tag = tickerCount;

        thisInfoLabel.tag = thisBar.tag + 100;
        thisTimeLabel.tag = thisBar.tag + 200;

        arrowView.tag = thisBar.tag + 300;
        UIImage *image = [UIImage imageNamed:@"LeftArrowRed.png"];


        // Add them to the scroller

        [self.scroller addSubview:thisBar];
        [self.scroller addSubview:thisInfoLabel];
        [self.scroller addSubview:thisTimeLabel];
        [self.scroller addSubview:arrowView];



        NSCalendar *calendar = [NSCalendar currentCalendar];


        if (!activity.stopTime) // Currently running
        {
            if ([activity.startTime timeIntervalSinceDate:timeframeDCStart] < 0)
            {
                dayBarDurationString = @"24:00:00";
                taDuration = (fabs([timeframeDCStart timeIntervalSinceDate:timeframeDCEnd]));
            }

            else
            {
                NSDateComponents *components= [calendar components:NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit fromDate:activity.startTime toDate:timeframeDCEnd options:0];
                NSInteger hours = [components hour];
                NSInteger minutes = [components minute];
                NSInteger seconds =[components second];
                dayBarDurationString = [NSString stringWithFormat:@"%02li:%02li:%02li",(long)hours,(long)minutes,(long)seconds];

                taDuration = (fabs([activity.startTime timeIntervalSinceDate:timeframeDCEnd]));
            }
        }

        else // Either early edge case or fully encapsulated
        {
            if ([activity.startTime timeIntervalSinceDate:timeframeDCStart] < 0) // Early edge case
            {
                NSDateComponents *components= [calendar components:NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit fromDate:timeframeDCStart toDate:activity.stopTime options:0];
                NSInteger hours = [components hour];
                NSInteger minutes = [components minute];
                NSInteger seconds =[components second];
                dayBarDurationString = [NSString stringWithFormat:@"%02li:%02li:%02li",(long)hours,(long)minutes,(long)seconds];

                taDuration = (fabs([activity.stopTime timeIntervalSinceDate:timeframeDCStart]));
            }

            else if ([activity.startTime  timeIntervalSinceDate:timeframeDCStart] > 0) // Encapsulated
            {
                NSDateComponents *components= [calendar components:NSDayCalendarUnit|NSHourCalendarUnit|NSMinuteCalendarUnit|NSSecondCalendarUnit fromDate:activity.startTime toDate:activity.stopTime options:0];
                NSInteger hours = [components hour];
                NSInteger minutes = [components minute];
                NSInteger seconds =[components second];
                dayBarDurationString = [NSString stringWithFormat:@"%02li:%02li:%02li",(long)hours,(long)minutes,(long)seconds];

                taDuration = [activity.duration doubleValue];
            }

        }


        NSDateFormatter *localDateFormatter = [[NSDateFormatter alloc] init];
        [localDateFormatter setDateFormat:@"MMM dd"];

        NSDateFormatter *localTimeFormatter = [[NSDateFormatter alloc] init];
        [localTimeFormatter setDateFormat:@"hh:mm a"];



        NSString * timeLabelDateString = [[NSString alloc]init];

        NSString * timeLabelTimeString = [[NSString alloc]init];

        NSString * timeLabelString = [[NSString alloc]init];


        if (([activity.startTime timeIntervalSinceDate:timeframeDCStart] < 0))
        {
            timeLabelDateString = [localDateFormatter stringFromDate:timeframeDCStart];
            timeLabelTimeString = [localTimeFormatter stringFromDate:timeframeDCStart];
        }

        else
        {
            timeLabelDateString = [localDateFormatter stringFromDate:activity.startTime];
            timeLabelTimeString = [localTimeFormatter stringFromDate:activity.startTime];
        }


        timeLabelString = [NSString stringWithFormat:@"%@ - %@",timeLabelDateString,timeLabelTimeString];

        thisBar.endColor = activity.color;

        activityPercent = fabs((taDuration / timeFrameDC) * 100);

        if (activityPercent > 100)
        {
            activityPercent = 100;
        }

        if (activityPercent < 1.9)
        {
            activityPercent = 1.9;
        }


        barHeight = abs((self.scroller.contentSize.height - 100) * (activityPercent / 100));

        if (tickerCount == 1)
        {
            barY = self.scroller.contentSize.height -10;
        }

        else
        {
            barY = (lastBarY - lastHeight);
        }



#pragma mark DisplayDayChart animation


        [UIView animateWithDuration:.8
                              delay:0.0
                            options: UIViewAnimationCurveEaseInOut // Deprecated, but still works
                         animations:^
         {
             // Starting state *************************************

             thisBar.frame = CGRectMake(20, self.scroller.contentSize.height, 130, 0);
             thisBar.backgroundColor = [UIColor blackColor];

             thisInfoLabel.frame = CGRectMake(20, self.scroller.contentSize.height, thisBar.frame.size.width, 30);

             thisTimeLabel.frame = CGRectMake((thisBar.frame.origin.x) + (thisBar.frame.size.width + 35), self.scroller.contentSize.height, 150, 15);

             thisTimeLabel.textColor = [UIColor blueColor];

             arrowView.frame = CGRectMake(thisTimeLabel.frame.origin.x - 20, thisTimeLabel.frame.origin.y, 16, 16);


             thisInfoLabel.textColor = [UIColor clearColor];


             // End state *************************************

             thisBar.frame = CGRectMake(20, barY, 130, - barHeight);
             thisBar.backgroundColor = thisBar.endColor;

             thisBar.layer.shadowColor = [[UIColor blackColor] CGColor];
             thisBar.layer.frame = CGRectInset(thisBar.layer.frame, 0.0, 3.0);

             thisBar.layer.shadowOpacity = 0.7;
             thisBar.layer.shadowRadius = 4.0;
             thisBar.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);


             // Position infoLabel

             thisInfoLabel.frame = CGRectMake(20, self.scroller.contentSize.height + ((thisBar.frame.size.height / 2) - 30), thisBar.frame.size.width, 30);


             thisTimeLabel.frame = CGRectMake((thisBar.frame.origin.x) + (thisBar.frame.size.width + 35), (thisBar.frame.origin.y) + (thisBar.frame.size.height) - 5, 150, 15);

             thisInfoLabel.textColor = [UIColor blackColor];

             dayBarNameString = activity.name;

             [thisInfoLabel setFont:[UIFont fontWithName:@"Noteworthy-Bold" size:16.0]];

             thisInfoLabel.numberOfLines = 0;

             thisInfoLabel.textAlignment = NSTextAlignmentCenter;
//             thisInfoLabel.layer.borderWidth = 1;


             thisInfoLabel.text = [NSString stringWithFormat:@"%@\n%@",dayBarNameString,dayBarDurationString];

             [thisInfoLabel sizeToFit];


             if ( thisInfoLabel.frame.size.height >= thisBar.frame.size.height)
             {
                 thisInfoLabel.text = [NSString stringWithFormat:@"%@",dayBarNameString];
             }

             else
             {
                 thisInfoLabel.text = [NSString stringWithFormat:@"%@\n%@",dayBarNameString,dayBarDurationString];
             }

             [thisInfoLabel sizeToFit];
             [thisInfoLabel setHidden:NO];

             thisInfoLabel.center = thisBar.center;

             arrowView.frame = CGRectMake(thisTimeLabel.frame.origin.x - 20, thisTimeLabel.frame.origin.y, 16, 16);
             arrowView.backgroundColor = [UIColor colorWithPatternImage:image];

             thisTimeLabel.textColor = [UIColor blueColor];
             [thisTimeLabel setFont:[UIFont boldSystemFontOfSize:13]];
             thisTimeLabel.numberOfLines = 0;

             thisTimeLabel.textAlignment = NSTextAlignmentLeft;

             thisTimeLabel.text = timeLabelString;

             [thisTimeLabel sizeToFit];


         }
                         completion:^(BOOL finished)
         {

             [self repositionLabels];

         }];


        [dayBarArray addObject:thisBar];
        [infoLabelArray addObject:thisInfoLabel];
        [timeLabelArray addObject:thisTimeLabel];
        taDuration = 0;
        tickerCount ++;


        lastBarY = barY;
        lastHeight = barHeight;
        lastBarFrame = thisBar.frame;

        [self positionInitialDayBarView];

    }
}

OK, first off. 好,首先 You're losing a lot of convenience stuff by refusing to use Interface Builder. 拒绝使用Interface Builder将使您失去很多便利。 It's like saying "I only use Xcode 3 because it used to work for me so I just kept it.". 这就像说“我只使用Xcode 3,因为它曾经为我工作,所以我保留了它”。 Interface Builder is an awesome tool that makes creating UIs ridiculously easy. Interface Builder是一个很棒的工具,它使创建UI变得异常容易。

The actual problem 实际问题

You said that you are not using AutoLayout. 您说您没有使用自动版式。 That is not correct. 那是不对的。

The default behaviour (in iOS8 definitely and also iOS7 I think) is that when you create a UIView (or any UI element) and add it to a view then the system will take the auto resizing mask of the view and apply Auto Layout constraints to match the resizing mask. 默认行为(肯定是在iOS8和iOS7中)是当您创建UIView (或任何UI元素)并将其添加到视图时,系统将采用视图的auto resizing mask并将“自动布局”约束应用于匹配调整大小的蒙版。

This means that when you do... 这意味着当您这样做时...

UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 100, 21)];
[self.view addSubView:label];

Then the label has auto layout constraints added to it to fix it to the position (10, 10) and the size (100, 21). 然后,标签上添加了自动布局约束,以将其固定到位置(10,10)和大小(100,21)。

You can't then change the frame by doing... 然后,您无法通过以下方式更改框架:

label.frame = CGRectMake(10, 50, 100, 21);

Because the auto layout constraints will just put it right back where it started. 因为自动布局约束只会将其放回到起点。

Solutions 解决方案

There are two solutions to this. 有两种解决方案。

The correct solution is to learn how to use AutoLayout. 正确的解决方案是学习如何使用自动版式。 It has been around now for 3 years and is becoming more and more important to iOS development (even just on portrait iPhone apps you now have 4 different devices sizes to deal with). 它已经存在了3年,并且对于iOS开发变得越来越重要(即使在纵向iPhone应用程序上,您现在也可以处理4种不同的设备尺寸)。 If you don't start learning AutoLayout you will be left behind. 如果您不开始学习自动版式,您将被抛在后面。 It is pretty much a requirement for all iOS development now. 现在,这几乎是所有iOS开发的要求。

The incorrect and very very bad solution is to stop the system from adding these constraints. 错误且非常糟糕的解决方案是阻止系统添加这些约束。 You can do this by adding the line... 您可以通过添加以下行来做到这一点...

label.translatesAutoresizingMaskIntoConstraints = NO;

after creating the label (and all other interface elements). 创建标签(以及所有其他接口元素)之后。

This will stop those automatic constraints from being added and stop them interfering with your layout stuff. 这将阻止添加那些自动约束,并阻止它们干扰您的布局内容。

This is the wrong solution though. 但是,这是错误的解决方案。 If you are doing this you are just delaying the point at which you have to learn Auto Layout. 如果这样做,只会延迟学习自动版式的时间。

I still don't understand why the switch to iOS 8 brought about such a large change 我仍然不明白为什么改用iOS 8会带来如此大的变化

I expect that one reason for the difference is that setting the text of a label (which you are doing in your animation block) triggers layout immediately in iOS 8, which was not the case in iOS 7. Layout means that now all the constraints are instantly obeyed, thus putting everything back into its original position (because the constraints have not changed). 我希望造成这种差异的一个原因是,设置标签的文本(您在动画块中执行的操作)在iOS 8中立即触发布局,而在iOS 7中则不是这种情况。布局意味着现在所有约束都可以立即服从,从而将所有内容恢复到原始位置(因为约束未更改)。

See, for example, my answer here: https://stackoverflow.com/a/26964376/341994 例如,在这里查看我的答案: https : //stackoverflow.com/a/26964376/341994

And here: https://stackoverflow.com/a/27085648/341994 而在这里: https : //stackoverflow.com/a/27085648/341994

And here: https://stackoverflow.com/a/26953986/341994 而在这里: https : //stackoverflow.com/a/26953986/341994

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM