如何修复不更新 UI 的目标 c 应用程序?

Posted

技术标签:

【中文标题】如何修复不更新 UI 的目标 c 应用程序?【英文标题】:How can I fix objective c app not updating UI? 【发布时间】:2020-10-05 05:21:24 【问题描述】:

我正在尝试在 Objective C 上为我的应用程序创建一种奇怪的计时器。我希望它能够运行,当用户输入时间并单击开始按钮时,我希望它隐藏键盘和文本字段,以及开始按钮,并显示一个文本字段,然后开始倒计时并定期更新以当前“秒”显示的文本字段,然后当倒计时循环结束时,它应该说计时器完成。

我在整个过程中进行了大量测试,检查我的功能是否正常运行,并且它们都正确输出。但是我的应用程序在它应该隐藏键盘几秒钟后没有隐藏键盘,它没有隐藏开始按钮,并且在循环结束之前它不会在视觉上更新秒数。老实说,我不知道发生了什么……有人可以告诉我怎么回事吗?

谢谢!

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad 
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    
    self.view.backgroundColor = [UIColor blueColor];
    
    self.timerSecondsText.hidden = YES;
   

    


    



- (IBAction)startTimer:(id)sender 
    int timerSeconds = [self.timeFieldVisuals.text intValue];
    int timerSecondsFifth = timerSeconds/5;
    NSLog(@"timerSeconds is %i",timerSeconds);
    NSLog(@"timerSecondsFifth is %i",timerSecondsFifth);
    
    [_timeFieldVisuals resignFirstResponder];

    self.timeFieldVisuals.hidden = YES;
    self.timerSecondsText.hidden = NO;
    
    if(_timerSecondsText.hidden == NO && _timeFieldVisuals.hidden == YES)
        NSLog(@"visual updates successful");
        
    

    
    for (int timeElapsed = 0; timerSeconds > 0; timerSeconds--) 
              //timeElapsed is placeholder to avoid errors

    
    /* 5 divides, the timer delay increases as the
     timerSeconds decreases, but timer should run for
      user input in the end*/

              if(timerSeconds > timerSecondsFifth*4 && timerSeconds <= timerSecondsFifth*6)

                  [NSThread sleepForTimeInterval:0.4f];
                  self.timerSecondsText.text = [NSString stringWithFormat:@"%i", timerSeconds];
              
              
              else if(timerSeconds > timerSecondsFifth*3 && timerSeconds <= timerSecondsFifth*4)

                  [NSThread sleepForTimeInterval:0.6f];
              
              
              else if(timerSeconds > timerSecondsFifth*2 && timerSeconds <= timerSecondsFifth*3)
            
                  [NSThread sleepForTimeInterval:1.0f];
              
              
              else if(timerSeconds > timerSecondsFifth && timerSeconds <= timerSecondsFifth*2)
                  [NSThread sleepForTimeInterval:1.2f];
              
              
              else if(timerSeconds <= timerSecondsFifth)
                  [NSThread sleepForTimeInterval:1.8f];
              
        self.timerSecondsText.text = [NSString stringWithFormat:@"%i", timerSeconds];
        NSLog(@"timerSeconds is %i",timerSeconds);

    
    self.timerSecondsText.text = @"Timer Complete"; 



@end
    

【问题讨论】:

您正在使用 sleepForTimeInterval 调用冻结 UI 线程。当 UI 线程被冻结时,UI 不会更新,这不是很明显吗? @EugeneDudnyk 所以我想通了,但后来我决定调用 [self.view setNeedsDisplay] 和 [self.view layoutIfNeeded],理论上应该强制重绘显示,我在调用之前调用了它们单独的方法,我将循环插入其中。所以据我所知,这应该迫使它在开始循环之前重新绘制,但它不起作用。所以也许我应该尝试使用常规的 NSTimer 而不是使用 sleepForTimeInterval? 是的,NSTimer 对于此类任务来说要容易得多。 setNeedsDisplay 和 layoutIfNeeded 不会将 NSThread 从睡眠中唤醒。您还可以使用 CADisplayLink 强制重复调用所选方法选择器以重绘您的字符串和/或按钮,并在时间结束时停止 displayLink。 用睡眠函数阻塞主线程是错误的,其他是基于意见的 取决于框架。当 UIKit 按原样使用时,阻塞 mainThread 确实没有帮助。有些框架的 UIKit 更新没有在 mainThread 中运行。 【参考方案1】:

下面只是一个例子,你可以做什么来代替[NSThread sleepForTimeInterval:...]。使用CADisplayLinkNSTimer 而不是与NSThread 方法进行交互,因为通常UIKit 在MainThread 中运行。因此,将 MainThread 推入睡眠会导致很多问题,就像您已经遇到的那样。

#import "TimingViewController.h"

@interface TimingViewController ()
@property (nonatomic) CADisplayLink *displayLink;
@property (nonatomic) NSTimer *timer;
@end

@implementation TimingViewController 
    UIButton *btn;
    UILabel *label;
    UITextField *timerSecondsText;
    NSDate *starttime;
    BOOL timerFieldIsValid;
    NSTimeInterval futureInterval;


- (void)viewDidLoad 
    [super viewDidLoad];

    timerSecondsText = [[UITextField alloc] initWithFrame:CGRectMake(10, 10, 80, 40)];
    timerSecondsText.hidden = NO;
    [timerSecondsText addTarget:self action:@selector(validateEntry:) forControlEvents:(UIControlEventValueChanged)];
    [self.view addSubview:timerSecondsText];
    
    timerFieldIsValid = NO;
    label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 80, 40)];
    label.text = @"--:--";
    label.hidden = YES;
    [self.view addSubview:label];
    
    btn = [UIButton buttonWithType:UIButtonTypeSystem];
    btn.titleLabel.text = @"Start";
    btn.frame = CGRectMake(10,60,80,40);
    btn.hidden = NO;
    [btn addTarget:self action:@selector(startTimer) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:btn];


- (IBAction)startTimer 
    if (timerFieldIsValid) 
        if (!_timer) 
            [timerSecondsText resignFirstResponder];
            timerSecondsText.hidden = YES;
            starttime = [NSDate now];
            _timer = [NSTimer scheduledTimerWithTimeInterval:futureInterval target:self selector:@selector(timeIsOver) userInfo:nil repeats:NO];
            label.hidden = NO;
            btn.hidden = YES;
        
        if (!_displayLink) 
            self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updatePulse)];
            //_displayLink.preferredFramesPerSecond = 24;
            [_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        
    


-(IBAction)validateEntry:(UITextField*)textfield 
    if (textfield.text.length) 
        // textfield validation here..
        NSTimeInterval possibleInterval = [textfield.text integerValue];
        
        if (possibleInterval > 0) 
            futureInterval = possibleInterval;
            timerFieldIsValid = YES;
         else 
            timerFieldIsValid = NO;
        
     else 
        timerFieldIsValid = NO;
    


-(void)timeIsOver 
    [_timer invalidate];
    self.timer = nil;
    
    [_displayLink invalidate];
    self.displayLink = nil;
    
    label.hidden = YES;
    btn.hidden = NO;
    timerSecondsText.hidden = NO;


-(void)updatePulse 
    label.text = [NSString stringWithFormat:@"%d", starttime.timeIntervalSinceNow];


@end

【讨论】:

非常感谢!但实际上我使用 NSTimer 修复了它,我认为这是一种更简单的方法。我基本上创建了一个函数循环(不是一个循环,而是两个在满足条件之前一直被调用的函数),一个用于计时器,一个用于视觉更新,所以它现在可以完美更新! 很高兴你知道了。

以上是关于如何修复不更新 UI 的目标 c 应用程序?的主要内容,如果未能解决你的问题,请参考以下文章

动画更改颜色时如何更新UI

iTunes Connect 促销代码和应用程​​序更新

如何在不更新应用程序的情况下重新加载 Cordova 应用程序的 UI

如何修复不支持的语言 admob

更新到 Xcode 10 后 XCUITest 的测试目标似乎不起作用

如何在新的 iTunes Connect UI 中上传应用更新