CALayer的位置包含NaN当在两个ViewController中显示BottomSheet时 &



我有2个UIViewController,当按下某个按钮时,它都有底部表单。假设ViewControllerA被导航到ViewControllerB。当我打开ViewController A,然后导航到ViewControllerB,我在ViewControllerB中呈现BottomSheet时,它崩溃了。以下是我收到的完整信息:

异常NSException * "CALayer位置包含NaN: [NaN 438.667]。层:& lt; CALayer: 0 x6000029781e0;position = CGPoint (0 0);边界=CGRect (0 0;199.667 48);delegate =<_UIEditMenuListView:0 x138e26420;帧= (-99.8333 -24;199.667 48);Alpha = 0;层=& lt; CALayer: 0 x6000029781e0>在;sublayers = ();opaque = YES;不透明度= 0;shadowOpacity = 0.25;shadowOffset = CGSize(0 3);shadowRadius = 12>"0 x0000600002470fc0

当我单独打开ViewController而不必导航到对方和ViewControllerA没有一个底部表时,错误不会发生。下面是我的BottomSheet的代码。

public protocol MSBottomSheetListDelegate: AnyObject {
func didFinishedPickingValue(value: String, index: Int)
}
public class MSBottomSheetList: UIViewController {
let tableView: MSTableView = MSTableView()
var selectedValue: String = ""
var selectedIndex: Int = 0
public var delegate: MSBottomSheetListDelegate?
var dataArray: [String] = [""]

lazy var titleLabel: UILabel = {
let label = UILabel()
label.text = "Bottom Sheet Title"
label.font = .boldSystemFont(ofSize: 20)
return label
}()

lazy var buttonDone: MSButtonContained = {
let button: MSButtonContained = MSButtonContained()
button.setTitle("Pilih", for: .normal)
button.setTitleColor(MSColorFoundation.msCarbonStrong, for: .normal)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()

lazy var contentStackView: UIStackView = {
let spacer = UIView()
let stackView = UIStackView(arrangedSubviews: [tableView, buttonDone])
stackView.axis = .vertical
stackView.spacing = 12.0
return stackView
}()

lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.layer.cornerRadius = 16
view.clipsToBounds = true
return view
}()

let maxDimmedAlpha: CGFloat = 0.6
lazy var dimmedView: UIView = {
let view = UIView()
view.backgroundColor = .black
view.alpha = maxDimmedAlpha
return view
}()

// Constants
let defaultHeight: CGFloat = 300
let dismissibleHeight: CGFloat = 200
let maximumContainerHeight: CGFloat = UIScreen.main.bounds.height - 64
// keep current new height, initial is default height
var currentContainerHeight: CGFloat = 300

// Dynamic container constraint
var containerViewHeightConstraint: NSLayoutConstraint?
var containerViewBottomConstraint: NSLayoutConstraint?

public override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupConstraints()
// tap gesture on dimmed view to dismiss
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleCloseAction))
dimmedView.addGestureRecognizer(tapGesture)

setupPanGesture()
}

@objc func handleCloseAction() {
animateDismissView()
}

public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
animateShowDimmedView()
animatePresentContainer()
}

public func setListData(data: [String]) {
dataArray = data
}

func setupView() {
view.backgroundColor = .clear
tableView.delegate = self
tableView.dataSource = self
tableView.center = self.view.center
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
buttonDone.addTarget(self, action:#selector(buttonDoneTapped) , for: .touchUpInside)
}

func setupConstraints() {
// Add subviews
view.addSubview(dimmedView)
view.addSubview(containerView)
dimmedView.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false

containerView.addSubview(contentStackView)
contentStackView.translatesAutoresizingMaskIntoConstraints = false

// Set static constraints
NSLayoutConstraint.activate([
// set dimmedView edges to superview
dimmedView.topAnchor.constraint(equalTo: view.topAnchor),
dimmedView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
dimmedView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
dimmedView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// set container static constraint (trailing & leading)
containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
// content stackView
contentStackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 32),
contentStackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -20),
contentStackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 20),
contentStackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -20),

buttonDone.leadingAnchor.constraint(equalTo: contentStackView.leadingAnchor),
buttonDone.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor),

tableView.trailingAnchor.constraint(equalTo: contentStackView.trailingAnchor),
tableView.leadingAnchor.constraint(equalTo: contentStackView.leadingAnchor)
])

containerViewBottomConstraint = containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: defaultHeight)
containerViewHeightConstraint?.isActive = true
containerViewBottomConstraint?.isActive = true
}

func setupPanGesture() {
// add pan gesture recognizer to the view controller's view (the whole screen)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.handlePanGesture(gesture:)))
// change to false to immediately listen on gesture movement
panGesture.delaysTouchesBegan = false
panGesture.delaysTouchesEnded = false
view.addGestureRecognizer(panGesture)
}
// MARK: Pan gesture handler
@objc func handlePanGesture(gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
// Drag to top will be minus value and vice versa
print("Pan gesture y offset: (translation.y)")

// Get drag direction
let isDraggingDown = translation.y > 0
print("Dragging direction: (isDraggingDown ? "going down" : "going up")")

// New height is based on value of dragging plus current container height
let newHeight = currentContainerHeight - translation.y

// Handle based on gesture state
switch gesture.state {
case .changed:
// This state will occur when user is dragging
if newHeight < maximumContainerHeight {
// Keep updating the height constraint
containerViewHeightConstraint?.constant = newHeight
// refresh layout
view.layoutIfNeeded()
}
case .ended:
// This happens when user stop drag,
// so we will get the last height of container

// Condition 1: If new height is below min, dismiss controller
if newHeight < dismissibleHeight {
self.animateDismissView()
}
else if newHeight < defaultHeight {
// Condition 2: If new height is below default, animate back to default
animateContainerHeight(defaultHeight)
}
else if newHeight < maximumContainerHeight && isDraggingDown {
// Condition 3: If new height is below max and going down, set to default height
animateContainerHeight(defaultHeight)
}
else if newHeight > defaultHeight && !isDraggingDown {
// Condition 4: If new height is below max and going up, set to max height at top
animateContainerHeight(maximumContainerHeight)
}
default:
break
}
}

@objc func buttonDoneTapped() {
animateDismissView()
delegate?.didFinishedPickingValue(value: selectedValue, index: selectedIndex)
}

func animateContainerHeight(_ height: CGFloat) {
UIView.animate(withDuration: 0.4) {
// Update container height
self.containerViewHeightConstraint?.constant = height
// Call this to trigger refresh constraint
self.view.layoutIfNeeded()
}
// Save current height
currentContainerHeight = height
}

// MARK: Present and dismiss animation
func animatePresentContainer() {
// update bottom constraint in animation block
UIView.animate(withDuration: 0.3) {
self.containerViewBottomConstraint?.constant = 0
// call this to trigger refresh constraint
self.view.layoutIfNeeded()
}
}

func animateShowDimmedView() {
dimmedView.alpha = 0
UIView.animate(withDuration: 0.4) {
self.dimmedView.alpha = self.maxDimmedAlpha
}
}

func animateDismissView() {
// hide blur view
dimmedView.alpha = maxDimmedAlpha
UIView.animate(withDuration: 0.4) {
self.dimmedView.alpha = 0
} completion: { _ in
// once done, dismiss without animation
self.dismiss(animated: false)
}
// hide main view by updating bottom constraint in animation block
UIView.animate(withDuration: 0.3) {
self.containerViewBottomConstraint?.constant = self.defaultHeight
// call this to trigger refresh constraint
self.view.layoutIfNeeded()
}
}
}
extension MSBottomSheetList: UIPickerViewDelegate, UIPickerViewDataSource {
public func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}

public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return dataArray.count
}

public func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
let row = dataArray[row]
return row
}
}
extension MSBottomSheetList: UITableViewDataSource, UITableViewDelegate {
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataArray.count
}

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(
withIdentifier: "cell", for: indexPath
) as? UITableViewCell else {
fatalError()
}
cell.textLabel?.text = dataArray[indexPath.row]
return cell
}

public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let indexPath = tableView.indexPathForSelectedRow
let currentCell = tableView.cellForRow(at: indexPath!)! as UITableViewCell
selectedValue = currentCell.textLabel?.text ?? ""
selectedIndex = indexPath?.row ?? 0
}
}

下面是我在ViewController上显示BottomSheet的代码。

let bottomSheetDatePicker = MSBottomSheetDatePicker()
bottomSheetDatePicker.modalPresentationStyle = .overCurrentContext
self.present(bottomSheetDatePicker, animated: false)

我已经尝试了几件事来解决它,但它仍然产生相同的错误:

  1. 在viewwilldisappear中取消BottomSheet。
  2. 设置底部面板的固定高度。

这一行有问题。为什么在使用自动布局时要在tableView上设置center属性?还有,你确定tableView要放在stackView中吗?

tableView.center = self.view.center

同样,在setupConstraints中,你激活了containerViewHeightConstraint而没有创建它。containerViewHeightConstraint为nil

containerViewHeightConstraint?.isActive = true

相关内容

  • 没有找到相关文章

最新更新