iOS 11 大标题导航栏的图像

Posted

技术标签:

【中文标题】iOS 11 大标题导航栏的图像【英文标题】:Image for Navigation Bar with Large Title iOS 11 【发布时间】:2018-04-14 04:51:45 【问题描述】:

AppStore 应用在 NabBar 的右侧有一个带有大标题的图像的图标:

如果有人知道如何实施想法了解如何实施,将不胜感激。

顺便说一句:在 UIBarButtonItem 内为 UIButton 设置图像不起作用。已经试过了。该按钮粘在屏幕顶部:

【问题讨论】:

@GuidoLodetti 这有点复杂,所以我正在为它写一个教程。紧急吗?如果是,我可以在不解释的情况下共享代码。 @TungFam 代码就够了!只是想知道它是否是一个复杂且不稳定的解决方案,或者它是否会在 ios 的下一个版本中得到很好的支持 :) 我们需要它来更新应用程序!谢谢。 100 行代码:) 足够稳定。在这里联系我:tung.fam@uptech.team @TungFam 有关于 iOS 13 的更新吗?有人有消息吗? @Bonnke 我假设你在 Github 上创建了一个问题,我在那里回答:github.com/tungfam/ImageInNavigationBarDemo/issues/8 【参考方案1】:

经过几个小时的编码,我终于设法让它工作。我还决定写一个详细教程:link。如果您喜欢非常详细的说明,请遵循它。

演示:

GitHub 上的完整项目:link。

这里有 5 个步骤来完成它:

第 1 步:创建图像

private let imageView = UIImageView(image: UIImage(named: "image_name"))

第 2 步:添加常量

/// WARNING: Change these constants according to your project's design
private struct Const 
    /// Image height/width for Large NavBar state
    static let ImageSizeForLargeState: CGFloat = 40
    /// Margin from right anchor of safe area to right anchor of Image
    static let ImageRightMargin: CGFloat = 16
    /// Margin from bottom anchor of NavBar to bottom anchor of Image for Large NavBar state
    static let ImageBottomMarginForLargeState: CGFloat = 12
    /// Margin from bottom anchor of NavBar to bottom anchor of Image for Small NavBar state
    static let ImageBottomMarginForSmallState: CGFloat = 6
    /// Image height/width for Small NavBar state
    static let ImageSizeForSmallState: CGFloat = 32
    /// Height of NavBar for Small state. Usually it's just 44
    static let NavBarHeightSmallState: CGFloat = 44
    /// Height of NavBar for Large state. Usually it's just 96.5 but if you have a custom font for the title, please make sure to edit this value since it changes the height for Large state of NavBar
    static let NavBarHeightLargeState: CGFloat = 96.5

第 3 步:设置用户界面:

private func setupUI() 
    navigationController?.navigationBar.prefersLargeTitles = true

    title = "Large Title"

    // Initial setup for image for Large NavBar state since the the screen always has Large NavBar once it gets opened
    guard let navigationBar = self.navigationController?.navigationBar else  return 
    navigationBar.addSubview(imageView)
    imageView.layer.cornerRadius = Const.ImageSizeForLargeState / 2
    imageView.clipsToBounds = true
    imageView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        imageView.rightAnchor.constraint(equalTo: navigationBar.rightAnchor,
                                         constant: -Const.ImageRightMargin),
        imageView.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor,
                                          constant: -Const.ImageBottomMarginForLargeState),
        imageView.heightAnchor.constraint(equalToConstant: Const.ImageSizeForLargeState),
        imageView.widthAnchor.constraint(equalTo: imageView.heightAnchor)
        ])

第四步:创建图片大小调整方法

private func moveAndResizeImage(for height: CGFloat) 
    let coeff: CGFloat = 
        let delta = height - Const.NavBarHeightSmallState
        let heightDifferenceBetweenStates = (Const.NavBarHeightLargeState - Const.NavBarHeightSmallState)
        return delta / heightDifferenceBetweenStates
    ()

    let factor = Const.ImageSizeForSmallState / Const.ImageSizeForLargeState

    let scale: CGFloat = 
        let sizeAddendumFactor = coeff * (1.0 - factor)
        return min(1.0, sizeAddendumFactor + factor)
    ()

    // Value of difference between icons for large and small states
    let sizeDiff = Const.ImageSizeForLargeState * (1.0 - factor) // 8.0

    let yTranslation: CGFloat = 
        /// This value = 14. It equals to difference of 12 and 6 (bottom margin for large and small states). Also it adds 8.0 (size difference when the image gets smaller size)
        let maxYTranslation = Const.ImageBottomMarginForLargeState - Const.ImageBottomMarginForSmallState + sizeDiff
        return max(0, min(maxYTranslation, (maxYTranslation - coeff * (Const.ImageBottomMarginForSmallState + sizeDiff))))
    ()

    let xTranslation = max(0, sizeDiff - coeff * sizeDiff)

    imageView.transform = CGAffineTransform.identity
        .scaledBy(x: scale, y: scale)
        .translatedBy(x: xTranslation, y: yTranslation)

第五步:

override func scrollViewDidScroll(_ scrollView: UIScrollView) 
    guard let height = navigationController?.navigationBar.frame.height else  return 
    moveAndResizeImage(for: height)

希望它清楚并帮助您! 如果您有任何其他问题,请在 cmets 中告诉我。

【讨论】:

我看到你在微积分上这么聪明。你能给我一个学习的建议吗? @seyha 对不起,你是什么意思? 我的意思是你擅长数学。你能给我一个好的学习建议吗? @seyha 哦,我明白了。谢谢:) 但老实说,我只在学校和大学里学会了它。所以也许在你的情况下,我会建议一些在线数学课程。 我想做这样的东西但是图像太大了,当用户滚动表格视图时,图像会在屏幕的中心,图像会很小,并且会像这样@TungFam跨度> 【参考方案2】:

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

请注意,为了能够完成此行为,我们需要将其添加到私有类“_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)
    

预览

【讨论】:

【参考方案3】:

您可以使用自定义视图创建 UIBarButtonItem。此自定义视图将是一个 UIView,其中实际的 UIButton(作为子视图)放置在距离顶部 x 个像素(x=您想要将其向下移动的像素数)。

【讨论】:

感谢您提出的解决方案,但不幸的是它不起作用。它不允许在栏按钮项的底部下方移动任何东西(在大标题上方) 嗯..抱歉。有时间我再看一遍。 没问题,感谢您花时间和精力研究这个问题! 尝试在 uibutton 中使用 imageInsets,或者在 titleView 中使用自定义视图。【参考方案4】:

感谢@TungFam,我想我有更好的解决方案。 check it out

两点:

    根据导航栏高度改变按钮框架

      // adjust topview height
    override func scrollViewDidScroll(_ scrollView: UIScrollView) 
        guard  let navBar = self.navigationController?.navigationBar else 
            return
        
        // hardcoded .. to improve
        if navBar.bounds.height > 44 + 40 + 10 
            NSLayoutConstraint.deactivate(heightConstraint)
            heightConstraint = [topview.heightAnchor.constraint(equalToConstant: 40)]
            NSLayoutConstraint.activate(heightConstraint)
         else 
            NSLayoutConstraint.deactivate(heightConstraint)
            var  height = navBar.bounds.height - 44 - 10
            if height < 0 
                height = 0
            
            heightConstraint = [topview.heightAnchor.constraint(equalToConstant: height)]
            NSLayoutConstraint.activate(heightConstraint)
    
        
    
    
    

    根据弹出/推送进度更改按钮 alpha

    @objc func onGesture(sender: UIGestureRecognizer) 
        switch sender.state 
        case .began, .changed:
            if let ct = navigationController?.transitionCoordinator 
                topview.alpha =  ct.percentComplete
            
        case .cancelled, .ended:
            return
        case .possible, .failed:
            break
        
    
    

【讨论】:

以上是关于iOS 11 大标题导航栏的图像的主要内容,如果未能解决你的问题,请参考以下文章

iOS 13 大型导航栏外观 + Tabbar 图像

iOS 自定义形状导航栏

如何将 UIImageView 添加到导航栏的右侧?

获取导航栏的默认返回指示器图像

iOS11中没有状态栏的导航栏与安全区域重叠

带有 SearchBar 的 iOS 11 导航栏大小