AppStore 应用在 NabBar 的右侧有一个带有大标题的图像的图标:
如果有人知道如何实现它或如何实现它的想法,我将不胜感激。
顺便说一句:在 UIBarButtonItem 内为 UIButton 设置图像不起作用。已经试过了。该按钮粘在屏幕顶部:
经过几个小时的编码,我终于设法使它工作。我还决定写一个详细的教程:链接。如果您喜欢非常详细的说明,请遵循它。
GitHub 上的完整项目:链接。
这里有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
}
第三步:设置界面:
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)
])
}
第 4 步:创建图像大小调整方法
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)
}
第 5 步:
override func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let height = navigationController?.navigationBar.frame.height else { return }
moveAndResizeImage(for: height)
}
希望它很清楚,对你有帮助!如果您有任何其他问题,请在评论中告诉我。
如果有人仍在寻找如何在 SwiftUI 中执行此操作。我制作了一个名为 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)
}
}
您可以使用自定义视图创建 UIBarButtonItem。这个自定义视图将是一个 UIView,其中实际的 UIButton(作为子视图)从顶部放置 x 个像素(x=您想要将其向下移动的像素数)。
感谢@TungFam,我想我有更好的解决方案。看看这个
两点:
根据导航栏高度更改按钮框架
// 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
}
}