【问题标题】:How to open view controllers in a container view without storyboard [closed]如何在没有情节提要的容器视图中打开视图控制器[关闭]
【发布时间】:2019-12-31 04:09:33
【问题描述】:

我想通过单击按钮而不使用情节提要在现有视图控制器上打开视图控制器。我该怎么做呢?这就是我的意思:

假设我们有三个可以滚动的视图控制器:

“zeroVC”、“oneVC”和“twoVC”

当我按下“twoVC”上的按钮时,我现在想在以下之间滚动:

“zeroVC”、“oneVC”和“threeVC”

我尝试通过堆栈溢出查看所有内容,但它们都使用情节提要。

【问题讨论】:

  • “替换”是什么意思?
  • 在 android 编程中,您可以使用片段替换视图。在 ios 中,我想单击屏幕上的一个按钮并在此屏幕上打开一个新的不同屏幕。
  • 我想这就是你要找的东西:stackoverflow.com/questions/30475235/…
  • 是的,你可以。在上面的链接中,它讨论了以编程方式嵌入容器视图控制器。使用它,您可以创建一个带有滚动视图的视图控制器,在滚动视图中创建一个内容视图,并将三个容器视图控制器放入具有自动布局约束的滚动视图中。
  • 我从您的链接中尝试了 Rivera 的回答并得到错误“'UIView 类型的值?'没有成员'addSubview'”。谢谢你的帮助杰,我是新来的 swift

标签: ios swift cocoa-touch


【解决方案1】:

假设我们有四个视图控制器:RedViewControllerGreenViewControllerBlueViewController,还有一个包含所有视图控制器,ContainerViewController

虽然您提到了一个包含三个子项的滚动视图控制器,但我们将使其设置为两个屏幕以保持简单。 以下方法是可扩展的,因此您可以轻松地采用任意数量的视图控制器。

我们的RedViewController 有 7 行:

class RedViewController: UIViewController {
  override func loadView() {
    let view = UIView()
    view.backgroundColor = .red
    self.view = view
  }
}

在我们继续讨论GreenViewControllerBlueViewController 之前,我们将定义protocol SwapViewControllerDelegate

protocol SwapViewControllerDelegate: AnyObject {
  func swap()
}

GreenViewControllerBlueViewController 将有一个符合此协议的 delegate,它将处理交换。 我们将使ContainerViewController符合这个协议。

请注意,SwapViewControllerDelegate 在其继承列表中有 AnyObject 以使其成为仅限类的协议——因此我们可以使委托变弱,以避免内存保留循环。

以下是GreenViewController

class GreenViewController: UIViewController {
  weak var delegate: SwapViewControllerDelegate?

  override func loadView() {
    let view = UIView()
    view.backgroundColor = .green
    self.view = view
  }

  override func viewDidLoad() {
    super.viewDidLoad()

    let button = UIButton()
    button.setTitle("Swap Me!", for: .normal)
    button.setTitleColor(.black, for: .normal)
    button.titleLabel?.font = .boldSystemFont(ofSize: 50)
    button.addTarget(
      self,
      action: #selector(swapButtonWasTouched),
      for: .touchUpInside)

    view.addSubview(button)

    // Put button at the center of the view
    button.translatesAutoresizingMaskIntoConstraints = false
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
  }

  @objc private func swapButtonWasTouched(_ sender: UIButton) {
    delegate?.swap()
  }
}

它有weak var delegate: SwapViewControllerDelegate?,它会在viewDidLoad中添加的按钮被触摸时处理交换,触发swapButtonWasTouched方法。

BlueViewController 同样实现:

class BlueViewController: UIViewController {
  weak var delegate: SwapViewControllerDelegate?

  override func loadView() {
    let view = UIView()
    view.backgroundColor = .blue
    self.view = view
  }


  override func viewDidLoad() {
    super.viewDidLoad()

    let button = UIButton()
    button.setTitle("Swap Me!", for: .normal)
    button.setTitleColor(.white, for: .normal)
    button.titleLabel?.font = .boldSystemFont(ofSize: 50)
    button.addTarget(
      self,
      action: #selector(swapButtonWasTouched),
      for: .touchUpInside)

    view.addSubview(button)

    // Put button at the center of the view
    button.translatesAutoresizingMaskIntoConstraints = false
    button.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
    button.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
  }

  @objc private func swapButtonWasTouched(_ sender: UIButton) {
    delegate?.swap()
  }
}

唯一的区别是viewbackgroundColorbuttontitleColor

最后,我们来看看ContainerViewControllerContainerViewController 有四个属性:

class ContainerViewController: UIViewController {

  let redVC = RedViewController()
  let greenVC = GreenViewController()
  let blueVC = BlueViewController()

  private lazy var scrollView: UIScrollView = {
    let scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.bounces = false
    scrollView.isPagingEnabled = true
    return scrollView
  }()
...
}

scrollView 是包含子视图控制器redVCgreenVCblueVC 的视图。 我们将使用自动布局,所以不要忘记将translatesAutoresizingMaskIntoConstraints 标记为false

现在,设置scrollView 的自动布局约束:

class ContainerViewController: UIViewController {
...
  private func setupScrollView() {
    view.addSubview(scrollView)
    let views = ["scrollView": scrollView]
    [
      NSLayoutConstraint.constraints(
        withVisualFormat: "H:|[scrollView]|",
        metrics: nil,
        views: views),
      NSLayoutConstraint.constraints(
        withVisualFormat: "V:|[scrollView]|",
        metrics: nil,
        views: views),
    ]
      .forEach { NSLayoutConstraint.activate($0) }
  }
...
}

我使用了 VFL,但您可以手动设置自动布局约束,就像我们对上面的按钮所做的那样。 使用自动布局,我们不必自己设置滚动视图的contentSize。 有关在 UIScrollView 中使用自动布局的更多信息,请参阅 Technical Note TN2154: UIScrollView And Autolayout

现在最重要的setupChildViewControllers()

class ContainerViewController: UIViewController {
...
  private func setupChildViewControllers() {
    [redVC, greenVC, blueVC].forEach { addChild($0) }

    let views = [
      "redVC": redVC.view!,
      "greenVC": greenVC.view!,
      "blueVC": blueVC.view!,
    ]
    views.values.forEach {
      scrollView.addSubview($0)
      $0.translatesAutoresizingMaskIntoConstraints = false
      $0.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
      $0.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
    }

    [
      NSLayoutConstraint.constraints(
        withVisualFormat: "H:|[redVC][greenVC]|",
        options: .alignAllTop,
        metrics: nil,
        views: views),
      NSLayoutConstraint.constraints(
        withVisualFormat: "H:|[redVC][blueVC]|",
        options: .alignAllTop,
        metrics: nil,
        views: views),
      NSLayoutConstraint.constraints(
        withVisualFormat: "V:|[redVC(==greenVC,==blueVC)]|",
        metrics: nil,
        views: views),
    ]
      .forEach { NSLayoutConstraint.activate($0) }

    [redVC, greenVC, blueVC].forEach { $0.didMove(toParent: self) }

    greenVC.view.isHidden = true

    greenVC.delegate = self
    blueVC.delegate = self
  }
...
}

我们首先将每个[redVC, greenVC, blueVC] 添加为ContainerViewController 的子视图控制器。 然后将子视图控制器的view 添加到scrollView。 将子视图控制器的widthAnchorheightAnchor 设置为view.widthAnchorview.heightAnchor,以使它们全屏显示。 此外,这在屏幕旋转时也可以使用。

使用views 字典,我们使用VFL 来设置自动布局约束。 我们将greenVC.view 放在redVC.view:H:|[redVC][greenVC]| 的右边,blueVC.view:H:|[redVC][blueVC]| 也是如此。 要修复greenVC.viewblueVC.view 的垂直位置,请将.alignAllTop 选项添加到约束中。 然后为redVC.view应用垂直布局,并设置greenVC.viewblueVC.view的高度:"V:|[redVC(==greenVC,==blueVC)]|。 设置了垂直位置,因为我们在设置水平约束时使用了.alignAllTop

在添加 then 作为子视图控制器之后,我们应该在子视图控制器上调用 didMove(toParent:) 方法。 (如果您想知道didMove(toParent:)addChild(_:) 方法的作用,显然它们作用很小;请参阅What does addChildViewController actually do?didMoveToParentViewController and willMoveToParentViewController。)

最后,隐藏greenVC.view,并将greenVC.delegateblueVC.delegate 设置为self。 那么当然,我们需要ContainerViewController符合SwapViewControllerDelegate

extension ContainerViewController: SwapViewControllerDelegate {
  func swap() {
    greenVC.view.isHidden.toggle()
    blueVC.view.isHidden.toggle()
  }
}

就是这样! 整个项目上传here

我建议阅读Implementing a Container View Controller,Apple 对此进行了详细记录。 (它是用 Objective-C 编写的,但实际上翻译成 Swift 很简单)

【讨论】:

  • 我让它和三个孩子一起工作!!你不知道我有多开心。谢谢你教我。
  • 很高兴听到它有帮助! :)
  • 可以在容器视图控制器中设置scrollViewcontentOffset属性。
  • 如果有帮助,欢迎您投票!
  • 这可能是因为您在设置scrollView 的框架之前设置了contentOffset。试试UIScreen.main.bounds.width * 2 而不是scrollView.frame.size.width*2
猜你喜欢
  • 1970-01-01
  • 2012-02-02
  • 2014-10-24
  • 2014-08-12
  • 1970-01-01
  • 1970-01-01
  • 2019-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多