Как я могу исправить проблему «removeConstraint () не работает» с моим UICollectionView?

#ios #swift #nslayoutconstraint

#iOS #swift #nslayoutconstraint

Вопрос:

Программно (без раскадровки) я пытаюсь удалить один набор ограничений и создать новый набор при каждом повороте устройства. Код, который я использую для удаления ограничений, выполняется без ошибок, но фактически не удаляет ограничения. Это приводит к ошибке компоновки при добавлении нового набора (конфликтующих) ограничений.

Я использую Xcode 10.1 и Swift 4.2 без раскадровки. В моем MainViewController я создаю и размещаю UICollectionView с именем ticketsView из функций, вызываемых viewDidLoad() . После addChild() и addSubview() я вызываю функцию с именем applyTicketsViewConstraints() , которая определяет текущую ориентацию устройства, а затем (как ожидается) удаляет все существующие ограничения и применяет соответствующий набор ограничений для текущей ориентации устройства.

Я переопределяю, viewWillTransition:toSize:withCoordinator() который вызывает ту же applyTicketsViewConstraints() функцию из закрытия координатора всякий раз, когда устройство поворачивается.

При запуске приложения ограничения правильно применяются на основе начальной ориентации устройства. При первом повороте устройства на 90 градусов дисплей также успешно изменяется, но любое последующее вращение приводит к «[LayoutConstraints] Невозможно одновременно выполнить ограничения» сообщение об ошибке.

Я попытался перебрать существующие ограничения ticketsView и удалить их по одному, и я попытался удалить их все сразу — но ни один из подходов не приводит к фактическому удалению ограничений. Они оба, похоже, выполняются успешно, но ограничения все еще действуют после их завершения.

     func createTicketCollectionWindow() {
        ticketsLayout = UICollectionViewFlowLayout()
        ticketsVC = TicketCollectionController(collectionViewLayout: ticketsLayout)
        ticketsVC?.collectionView?.backgroundColor = UIColor.darkGray
        ticketsView = ticketsVC!.view
        ticketsView.translatesAutoresizingMaskIntoConstraints = false
        addChild(ticketsVC!)
        self.view.addSubview(ticketsView)
        applyTicketsViewConstraints()
    }
  
     func applyTicketsViewConstraints() {
        let safe = self.view.safeAreaLayoutGuide
        let deviceSize = UIScreen.main.bounds.size
        for individualConstraint in ticketsView.constraints {
            print("TicketViewConstraints:   Removing Constraint: (individualConstraint)" )
            // ticketsView.removeConstraint(individualConstraint)
        }
        ticketsView.removeConstraints(ticketsView.constraints)
        // self.view.removeConstraints(ticketsView.constraints)
        if deviceSize.width < deviceSize.height {
            print("TicketsView Constraint:  Device is in PORTRAIT orientation!")
            ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width).isActive = true
            ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 ).isActive = true
            ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0).isActive = true
            ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.75).isActive = true
        } else {
            print("TicketsView Constraint:  Device is in LANDSCAPE orientation!")
            ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width).isActive = true
            ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 ).isActive = true
            ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0).isActive = true
            ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.65).isActive = true
        }
        ticketsView.layoutIfNeeded()
    }
  
     override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransition(to: size, with: coordinator)
        coordinator.animate(alongsideTransition: { (_) in
            self.applyTicketsViewConstraints()
            self.ticketsVC?.collectionView.reloadData()
        }) { (_) in
            self.ticketsView.layoutIfNeeded()
        }
    }
  

Я ожидаю, что 4 ограничения, которые я создаю в приведенном выше коде, будут удалены, чтобы я мог воссоздать их с другими значениями. Кажется, я не могу найти способ удалить их (который работает). 2 строки выше, которые закомментированы в приведенном выше коде, показывают, что я также пробовал их (безуспешно).

Ответ №1:

Вы можете попробовать

 var portrait = [NSLayoutConstraint]()
var landscape = [NSLayoutConstraint]()

let porCon1  = ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width) 
let porCon2  =  ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 ) 
let porCon3  = ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0) 
let porCon4  = ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.75) 

portrait = [porCon1,porCon2,porCon3,porCon4]

let landCon1  = ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width) 
let landCon1  = ticketsView.trailingAnchor.constraint(equalTo:safe.trailingAnchor, constant: 0 ) 
let landCon1  = ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0) 
let landCon1  = ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.65) 

landscape = [landCon1,landCon2,landCo3,landCon4]
  

 func setPortrait(_ por:Bool) {  
    portrait.forEach { $0.isActive = por }
    landscape.forEach { $0.isActive = !por } 
}
  

Может быть также

 NSLayoutConstraint.deActivate(portrait)
NSLayoutConstraint.deActivate(landscape)
if por {
   NSLayoutConstraint.activate(portrait)
}
else { 
  NSLayoutConstraint.activate(landscape)
}
  

Комментарии:

1. Ваше решение работает — спасибо — но правильный способ сделать это — использовать функции NSLayoutConstraint.activate() и NSLayoutConstraint.deactivate() для всего созданного вами массива, а не цикл forEach, который устанавливает каждый. Является активным значением независимо.

2. Не забывайте, что вы всегда должны деактивировать текущие ограничения перед активацией новых.

Ответ №2:

Я считаю, что вам нужно работать с ограничениями активации / деактивации, например:

Создайте для них переменные и установите значения ограничений в вашем setupConstrainsMethod. var notRotatedYConstraint: NSLayoutConstraint!

         var rotatedYConstraint: NSLayoutConstraint!
  

в viewdidload:
NSLayoutConstraint.activate([notRotatedYConstraint])

В вашей функции при повороте iPhone. NSLayoutConstraint.deactivate([notRotatedYConstraint]) NSLayoutConstraint.activate([rotatedYConstraint])

Ответ №3:

Первые 2 ответа, которые были опубликованы, были объединены, чтобы дать мне ответ, который мне был нужен. @Sh_Khan дал мне указание использовать массив NSLayoutConstraint. @Wurzel указал, что я должен использовать функции NSLayoutConstraint.activate() и .deactivate(), чтобы включить / отключить всю группу ограничений одновременно.

Итак, ключевым моментом было добавить все портретные ограничения в один массив, добавить все ландшафтные ограничения в другой массив, а затем каждый раз, когда меняется ориентация, я вызываю функцию деактивации в обоих массивах, а затем повторно создаю и активирую соответствующий массив.

(safeAreaLayoutGuide изменяется при изменении ориентации устройства, поэтому я не смог заранее создать оба набора ограничений заранее. Я также хотел найти решение, которое сработало бы, если бы ограничения стали более динамичными — например, реагируя на изменение пользователем размера области отображения)

Мой исходный код теперь становится:

     func applyTicketsViewConstraints() {
        let safe = self.view.safeAreaLayoutGuide
        let deviceSize = UIScreen.main.bounds.size

        NSLayoutConstraint.deactivate(portraitTicketsViewConstraints)
        NSLayoutConstraint.deactivate(landscapeTicketsViewConstraints)

        if deviceSize.width < deviceSize.height {
            print("TicketsView Constraint:  Device Size is in PORTRAIT orientation!")
            portraitTicketsViewConstraints = [
                ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width),
                ticketsView.trailingAnchor.constraint(equalTo: safe.trailingAnchor, constant: 0 ),
                ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0),
                ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.75)
            ]
            NSLayoutConstraint.activate(portraitTicketsViewConstraints)
        } else {
            print("TicketsView Constraint:  Device Size is in LANDSCAPE orientation!")
            landscapeTicketsViewConstraints = [
                ticketsView.leadingAnchor.constraint(equalTo: safe.trailingAnchor, constant: -safe.layoutFrame.width),
                ticketsView.trailingAnchor.constraint(equalTo: safe.trailingAnchor, constant: 0 ),
                ticketsView.bottomAnchor.constraint(equalTo: safe.bottomAnchor, constant: 0),
                ticketsView.heightAnchor.constraint(equalToConstant: safe.layoutFrame.height * 0.65)
            ]
            NSLayoutConstraint.activate(landscapeTicketsViewConstraints)
        }
        ticketsView.layoutIfNeeded()
    }
}