@uzolenta

Как обновить интерфейс при нажатии кнопки(проблема с потоками)?

Здравствуйте.
Помогите разобраться с потоками, пожалуйста.
Что хочется реализовать: при нажатии на кнопку запуск индикатора активности, выполнение кода, остановка индикатора. Вроде бы всё просто, но что-то я застрял.

@IBAction func button(_ sender: UIButton) {
acrivityIndicator.startAnimating()

//Код со множественными обращениями к серверу (URLSession.shared.dataTask)

acrivityIndicator.stopAnimating()
}


Соответственно, acrivityIndicator.startAnimating() срабатывает только в самом конце, после выполнения всего кода.
При нажатии на кнопку - блокируется весь интерфейс. Вычитал, что обновление интерфейса можно делать только в главном потоке:
DispatchQueue.main.async {
                self.acrivityIndicator.startAnimating()
            }

Но не срабатывает.

Единственная схема, при которой все работает:
@IBAction func button(_ sender: UIButton) {
DispatchQueue.global().async {
 DispatchQueue.main.async {
acrivityIndicator.startAnimating()
}
//Код со множественными обращениями к серверу (URLSession.shared.dataTask)

 DispatchQueue.main.async {
acrivityIndicator.stopAnimating()
}
}
}

Нет понимания ещё как остановить код внутри Global, если, например, произошла ошибка внутри URLSession.shared.dataTask.

Как правильно реализовать задуманное: чтобы пользователь не мог больше ничего делать, но появился индикатор в начале при нажатии на кнопку (до выполнения кода)?
Ведь при нажатии на кнопку и так показывает, что в главном потоке выполнение:
print(Thread.isMainThread)
        print(Thread.current)


ответ:

true
<NSThread: 0x60000203e280>{number = 1, name = main}
  • Вопрос задан
  • 602 просмотра
Пригласить эксперта
Ответы на вопрос 1
doublench21
@doublench21 Куратор тега Swift
Остановку индикатора нужно делать по завершению работы. Работа с URLSession является асинхронной, поэтому останавливать индикатор нужно в completion таска, который вы запускаете.

Все примеры показанные вами можно просто выкинуть. Это полнейший ужас.

Нельзя пользователю запрещать что-то делать. По крайне мере он должен иметь возможность переключаться между табами, если они есть.

Если вы хотите показать пользователю, что идет загрузка, то сделайте оверлай, который будет показываться во время работы таска и скроется по его окончанию. Примеров миллион. Первый из гугла:
https://stackoverflow.com/questions/27960556/loadi...

UPD:
5dbaf3a561387056015331.png

Текстом
class ViewController: UIViewController {
  
  @IBOutlet var button: UIButton!
  
  
  var alert: UIAlertController?
  
  func displayActivityIndicatorAlert() {
    alert = UIAlertController(title: "Deleting from black list...", message: nil, preferredStyle: .alert)
    let activityIndicator = UIActivityIndicatorView(style: .medium)
    activityIndicator.translatesAutoresizingMaskIntoConstraints = false
    activityIndicator.isUserInteractionEnabled = false
    activityIndicator.startAnimating()
    
    alert!.view.addSubview(activityIndicator)
    alert!.view.heightAnchor.constraint(equalToConstant: 95).isActive = true
    
    activityIndicator.centerXAnchor.constraint(equalTo: alert!.view.centerXAnchor, constant: 0).isActive = true
    activityIndicator.bottomAnchor.constraint(equalTo: alert!.view.bottomAnchor, constant: -20).isActive = true
    
    present(alert!, animated: true)
  }
  
  func dismissActivityIndicatorAlert() {
    alert?.dismiss(animated: true)
    alert = nil
  }
  
  var blackList = [Int](0...9)
  let lock = NSLock()
  
  func asyncDeleteBlackList() {
    var tasks = [URLSessionTask]()
    tasks.reserveCapacity(10)
    
    var results = [Int: String]()
    results.reserveCapacity(10)
    
    let group = DispatchGroup()
    
    displayActivityIndicatorAlert()
    
    for item in blackList {
      group.enter()
      
      let url = URL(string: "https://.../api/v1/today/batch")!
      let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let error = error {
          // Что-то делаем с ошибкой на клиенте...
          return
        }
        
        guard let httpResponse = response as? HTTPURLResponse,
          (200...299).contains(httpResponse.statusCode) else
        {
          // Что-то делаем с ошибкой на сервере...
          return
        }
        
        if let mimeType = httpResponse.mimeType,
          mimeType == "application/json",
          let data = data,
          let string = String(data: data, encoding: .utf8)
        {
          print("Запрос №\(item) завершён.")
          self.lock.lock()
          results[item] = string
          self.lock.unlock()
          group.leave()
        }
      }
      
      tasks.append(task)
      task.resume()
    }
    
    group.notify(queue: .main) {
      print("\nЗадача завершена.")
      self.dismissActivityIndicatorAlert()
    }
  }
  
  override func viewDidLoad() {
    button.addTarget(self, action: #selector(pressed(sender:)), for: .touchUpInside)
  }
  
  @objc
  func pressed(sender: UIButton) {
    asyncDeleteBlackList()
  }
}


Результат: (Видео)
https://disk.yandex.ru/i/S5yG9YQsRRJSZg
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы