【问题标题】:Adjust position of bar button item when using large titles with iOS 11在 iOS 11 中使用大标题时调整栏按钮项的位置
【发布时间】:2017-07-26 05:13:39
【问题描述】:

我在 iOS 11 中使用大标题导航栏,但是当我添加一个栏按钮项目时,它看起来很奇怪,位于与原始标题导航栏相同的位置。当标题很大时,我想将栏按钮项向下移动,并在导航栏不再很大时将其移回其原始位置。最好的方法是什么?

这是显示栏按钮项的奇怪位置的图像

我可以使用 viewWillLayoutSubviews() 动态获取导航栏高度,但我无法使用 setTitlePositionAdjustment 更改栏按钮项的位置

override func viewWillLayoutSubviews() {
    guard let navbarHeight = self.navigationController?.navigationBar.frame.height else{ return }
}

【问题讨论】:

  • 虽然很糟糕,但这是整个 iOS 中使用的“标准”导航视图 UI。你真的想偏离苹果自己的应用程序设定的标准吗? (例如,转到 Mail 的邮箱页面——它使用相同的视图。)
  • @BallpointBen 新应用商店的 UI 在导航栏上有一个向下移动的按钮,所以我不明白为什么会出现问题。
  • True... 在您看到 Apple 引入的硬件与此 UI 更改一起出现之前,您可能希望推迟此操作。或者至少将更改限制在 7 和更早版本。

标签: ios swift ios11 swift4


【解决方案1】:

为了解决我自己的问题,我只是添加了一个按钮作为导航栏的子视图,并为导航栏设置了右侧和底部约束。现在,当导航栏改变大小时,按钮将上下移动。但是,这需要在您从该视图控制器中显示的任何视图控制器中删除该按钮。因此,我向按钮添加了一个标签 1 并将其从另一个视图控制器的父视图中删除。这是最简单的解决方法,我发现它是最简单的方法。

设置右键:

func setupNavBar() {

    self.title = "Home"
    self.navigationController?.navigationBar.prefersLargeTitles = true
    self.navigationController?.navigationBar.isTranslucent = false

    let searchController = UISearchController(searchResultsController: nil)
    self.navigationItem.searchController = searchController

    let rightButton = UIButton()
    rightButton.setTitle("Right Button", for: .normal)
    rightButton.setTitleColor(.purple, for: .normal)
    rightButton.addTarget(self, action: #selector(rightButtonTapped(_:)), for: .touchUpInside)
    navigationController?.navigationBar.addSubview(rightButton)
    rightButton.tag = 1
    rightButton.frame = CGRect(x: self.view.frame.width, y: 0, width: 120, height: 20)

    let targetView = self.navigationController?.navigationBar

    let trailingContraint = NSLayoutConstraint(item: rightButton, attribute:
        .trailingMargin, relatedBy: .equal, toItem: targetView,
                         attribute: .trailingMargin, multiplier: 1.0, constant: -16)
    let bottomConstraint = NSLayoutConstraint(item: rightButton, attribute: .bottom, relatedBy: .equal,
                                    toItem: targetView, attribute: .bottom, multiplier: 1.0, constant: -6)
    rightButton.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([trailingContraint, bottomConstraint])

}

要从任何 show segued 视图控制器中删除它:

func removeRightButton(){
    guard let subviews = self.navigationController?.navigationBar.subviews else{return}
    for view in subviews{
        if view.tag != 0{
            view.removeFromSuperview()
        }
    }
} 

两个函数都在 viewWillAppear 函数中调用

【讨论】:

  • 视图出现后导航栏跳来跳去有什么问题吗?当视图出现时,我的导航栏向上移动了几个像素。否则效果很好!
  • 任何人在使用 iOS 14.6 时遇到此问题?它在调试模式下工作正常,但在生产中卡在大标题中
【解决方案2】:

我进行了一些挖掘,最终得出了与 Messages 应用程序中完全相同的行为(这意味着按钮位于导航栏下方而不是上方)。 唯一缺少的部分是UIBarButtonItem 出现时发生的漂亮动画/模糊...

公平警告:我目前的解决方案是使用私有类(名为 _UINavigationBarLargeTitleView),Apple 可能会因此拒绝您的应用程序......

// Make sure you have a `navigationBar`
guard let navigationBar = navigationController?.navigationBar else {
    return
}
// Make sure you get the correct class from the string, the class itself is not exposed…
guard let UINavigationBarLargeTitleView = NSClassFromString("_UINavigationBarLargeTitleView") else {
    return
}
// Then, you need to find the subview of type `_UINavigationBarLargeTitleView` :
navigationBar.subviews.forEach { subview in
    if subview.isKind(of: UINavigationBarLargeTitleView.self) {
        // If you have it, add whatever button you want (some example below)
        subview.addSubview(largeTitleViewRightBarButton)

        // Constrain it as you want
        NSLayoutConstraint.activate([
            largeTitleViewRightBarButton.bottomAnchor.constraint(equalTo: subview.bottomAnchor, constant: -10),
            largeTitleViewRightBarButton.trailingAnchor.constraint(
                equalTo: subview.trailingAnchor,
                constant: -view.directionalLayoutMargins.trailing
            )
        ])
    }
}

// Finally, the magic happens with one scrollView delegate method :
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView.contentOffset.y >= -103 { // Moving up
        navigationItem.rightBarButtonItem = rightBarButtonItem
    } else { // Moving down
        navigationItem.rightBarButtonItem = nil
    }
}

这是我制作按钮的方法:

private(set) lazy var image: UIImage? = {
    let config = UIImage.SymbolConfiguration(pointSize: 28, weight: .semibold, scale: .default)
    let image = UIImage(systemName: "magnifyingglass.circle.fill", withConfiguration: config)

    return image
}()

private(set) lazy var largeTitleViewRightBarButton: UIButton = {
    let button = UIButton(type: .custom)

    button.translatesAutoresizingMaskIntoConstraints = false
    button.imageView?.tintColor = R.color.appDodgerBlue()
    button.setImage(image, for: .normal)
    button.addTarget(presenter, action: #selector(presenter.onSearchRequested), for: .touchUpInside)

    return button
}()

private(set) lazy var rightBarButtonItem: UIBarButtonItem = {
    let barButtonItem = UIBarButtonItem(
        image: image,
        style: .plain,
        target: presenter,
        action: #selector(presenter.onSearchRequested)
    )
    return barButtonItem
}()

Demo

【讨论】:

    【解决方案3】:

    你要做的是设置BarButtonItem的标题位置调整。将以下行添加到 viewWillAppear 函数。使用verticalhorizontal 值来获得你喜欢的layout

    navigationItem.rightBarButtonItem?.setTitlePositionAdjustment(.init(horizontal: 10, vertical: 20), for: UIBarMetrics.default)

    https://developer.apple.com/documentation/uikit/uibarbuttonitem/1617149-settitlepositionadjustment

    【讨论】:

    • 这对我不起作用。栏按钮项目的位置没有改变。我还想知道如何检测导航栏何时显示大标题以及何时显示原始标题,以便我可以动态更改位置。感谢您的帮助!
    • 您可以观察navigationController.navigationBar.frame,以便在高度变化时得到通知。
    【解决方案4】:

    如果有人还在寻找如何在 SwiftUI 中执行此操作。我做了一个package named NavigationBarLargeTitleItems 来处理这个问题。它模仿了您在 AppStore 和 Messages-app 中看到的行为。

    在此示例中,您将看到一个配置文件图标,但您也可以使用创建食谱按钮等文本。如果您想让它在滚动后立即显示为标准导航栏项目,则需要 GeometryReader。如果您喜欢示例,请告诉我。

    请注意,为了能够完成此行为,我们需要将其添加到私有类“_UINavigationBarLargeTitleView”中,因此在提交到 App Store 时可能会导致您的应用被拒绝。

    我还在这里为那些不喜欢链接或只想复制/粘贴的人提供完整的相关源代码。

    扩展:

    // Copyright © 2020 Mark van Wijnen
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the “Software”), to deal
    // in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice and this permission notice shall be included in
    // all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    // SOFTWARE.
    import SwiftUI
    
    public extension View {
        func navigationBarLargeTitleItems<L>(trailing: L) -> some View where L : View {
            overlay(NavigationBarLargeTitleItems(trailing: trailing).frame(width: 0, height: 0))
        }
    }
    
    fileprivate struct NavigationBarLargeTitleItems<L : View>: UIViewControllerRepresentable {
        typealias UIViewControllerType = Wrapper
        
        private let trailingItems: L
        
        init(trailing: L) {
            self.trailingItems = trailing
        }
        
        func makeUIViewController(context: Context) -> Wrapper {
            Wrapper(representable: self)
        }
        
        func updateUIViewController(_ uiViewController: Wrapper, context: Context) {
        }
        
        class Wrapper: UIViewController {
            private let representable: NavigationBarLargeTitleItems?
            
            init(representable: NavigationBarLargeTitleItems) {
                self.representable = representable
                super.init(nibName: nil, bundle: nil)
            }
            
            required init?(coder: NSCoder) {
                self.representable = nil
                super.init(coder: coder)
            }
                    
            override func viewWillAppear(_ animated: Bool) {
                guard let representable = self.representable else { return }
                guard let navigationBar = self.navigationController?.navigationBar else { return }
                guard let UINavigationBarLargeTitleView = NSClassFromString("_UINavigationBarLargeTitleView") else { return }
               
                navigationBar.subviews.forEach { subview in
                    if subview.isKind(of: UINavigationBarLargeTitleView.self) {
                        let controller = UIHostingController(rootView: representable.trailingItems)
                        controller.view.translatesAutoresizingMaskIntoConstraints = false
                        subview.addSubview(controller.view)
                        
                        NSLayoutConstraint.activate([
                            controller.view.bottomAnchor.constraint(
                                equalTo: subview.bottomAnchor,
                                constant: -15
                            ),
                            controller.view.trailingAnchor.constraint(
                                equalTo: subview.trailingAnchor,
                                constant: -view.directionalLayoutMargins.trailing
                            )
                        ])
                    }
                }
            }
        }
    }
    

    用法:

    import SwiftUI
    import NavigationBarLargeTitleItems
    
    struct ContentView: View {
        var body: some View {
            NavigationView {
                List {
                    ForEach(1..<50) { index in
                        Text("Sample Row \(String(index))")
                    }
                }
                .navigationTitle("Navigation")
                .navigationBarLargeTitleItems(trailing: ProfileIcon())
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    
    struct ProfileIcon: View {
        var body: some View{
            Button(action: {
                print("Profile button was tapped")
            }) {
                Image(systemName: "person.circle.fill")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.red)
                    .frame(width: 36, height: 36)
            }
            .offset(x: -20, y: 5)
        }
    }
    

    预览

    【讨论】:

      【解决方案5】:

      如果导航标题很大,您可以调整导航标题,以便您的栏 按钮会自动调整。这是代码。还有 iOS 邮件 应用程序做同样的事情供您参考。

      func adjustsTitle() {
       guard let font = UIFont(name: "Helvetica-Medium", size: 16) else { return }
       let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 20))
       label.textColor = UIColor.black
       label.textAlignment = .center
       label.text = navigationItem.title
       label.adjustsFontSizeToFitWidth = true
       navigationItem.titleView = label
      }
      

      更新答案 如果您想调整标题下方的按钮(如果它变大了),那么在这种情况下,您需要在导航栏上加载自定义视图。

      //Hide back button. Since you are going to have custom button
      navigationItem.hidesBackButton = true
      //Increase the height based on your view intrinsic content size
      navigationController?.navigationBar.frame.size.height = 100
      guard let yourCustomView = UINib(nibName: "yourCustomXib", bundle: nil).instantiate(withOwner: nil, options: nil).first as? YourCustomView else {
       fatalError("Missing yourCustomXib")
      }
      navigationController?.navigationBar.addSubview(yourCustomView)
      

      【讨论】:

      • 什么是 DLLabel?我尝试改用 UILabel,但它并没有改变任何关于标题的内容。
      • 我的意思是 UILabel
      • 这仍然没有改变标题视图,仍然出现正常的大标题。
      • 你使用了adjustsFontSizeToFitWidth吗??因为基于固定帧它会自动调整
      • 我确实用过。虽然,我相信导航项甚至没有将 titleView 设置为新标签,因为我试图更改颜色和框架,但什么也没发生
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      • 2023-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-14
      相关资源
      最近更新 更多