如何在SwiftUI中将个人资料图标显示在大型导航栏标题旁边?

9

我正在开发一个支持多个配置文件的应用程序。我非常喜欢苹果公司在他们所有的应用程序中展示配置文件图标大导航栏标题的方式。看下面的截图:

截图示例

我的问题如下:

  • SwiftUI中是否可以实现这一点?如果可以,怎么做?
  • 如果在纯SwiftUI中无法实现,那么我该如何包含UIKit代码来实现它呢?

感谢您的帮助。

2个回答

20

我使用SwiftUI-Introspect解决了这个问题,它可以从SwiftUI中“审查基础的UIKit组件”。

以下是一个视图的示例:

struct ContentView: View {

    @State private var lastHostingView: UIView!
    
    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(1 ... 50, id: \.self) { index in
                    Text("Index: \(index)")
                }
                .frame(maxWidth: .infinity)
            }
            .navigationTitle("Large title")
            .introspectNavigationController { navController in
                let bar = navController.navigationBar
                let hosting = UIHostingController(rootView: BarContent())
                
                guard let hostingView = hosting.view else { return }
                // bar.addSubview(hostingView)                                          // <--- OPTION 1
                // bar.subviews.first(where: \.clipsToBounds)?.addSubview(hostingView)  // <--- OPTION 2
                hostingView.backgroundColor = .clear
                
                lastHostingView?.removeFromSuperview()
                lastHostingView = hostingView
                
                hostingView.translatesAutoresizingMaskIntoConstraints = false
                NSLayoutConstraint.activate([
                    hostingView.trailingAnchor.constraint(equalTo: bar.trailingAnchor),
                    hostingView.bottomAnchor.constraint(equalTo: bar.bottomAnchor, constant: -8)
                ])
            }
        }
    }
}

酒吧内容和个人资料图片浏览:

struct BarContent: View {
    var body: some View {
        Button {
            print("Profile tapped")
        } label: {
            ProfilePicture()
        }
    }
}

struct ProfilePicture: View {
    var body: some View {
        Circle()
            .fill(
                LinearGradient(
                    gradient: Gradient(colors: [.red, .blue]),
                    startPoint: .topLeading,
                    endPoint: .bottomTrailing
                )
            )
            .frame(width: 40, height: 40)
            .padding(.horizontal)
    }
}

.frame(width: 40, height: 40)hostingView.bottomAnchorconstant需要根据您的需求进行调整。

每个选项的结果(在代码中进行了注释):

选项1 选项2
滚动时视图固定 滚动时视图从下方消失
结果-选项1 结果-选项2

谢谢你提供这个绝妙的答案,这正是我在寻找的。然而,我遇到了一个问题,就是在使用Option 2 *(这是我想要使用的行为)*时:当导航到另一个也使用Large Title的视图时,一切都很顺利,但是当导航到一个带有Inline Title的视图,然后返回时,个人资料图标出现在原始视图的大标题上,不再在右侧。我该如何解决这个错误?那么,你的解决方案是完美的;) - christophriepe
这太棒了。这个解决方案如此简洁。这是唯一的问题。 - christophriepe
@christophriepe,看起来没有一个很好的解决方案,选项#2看起来非常糟糕。选项#1是最好的方式,我认为在滚动时有重叠不应该是太大的问题。您可以通过使用目标内容的.onAppearonDisappear来暂时隐藏BarContent()(图标不会出现在目标视图中)。我认为最好的解决方案是简单地选择选项#1,因为它的效果要好得多。毕竟,我们正在尝试访问私有视图,所以选项#2的解决方案有点hacky。 - George
无论如何,我还是会选择第二个选项。我只有几个带有内联样式的视图,这些我可以更改。但是,当在导航栏中放置搜索栏时,个人资料图标视图会显示在搜索栏视图内部。有没有什么方法来解决这个问题? - christophriepe
1
虽然这还不是一个理想的解决方案,但你已经赢得了奖励。如果你将来能想到其他的方法,我会很高兴在这里与你分享。 - christophriepe
显示剩余5条评论

4

没有NavigationView

我使用纯SwiftUI完成了这个。您需要用自己的图片替换Image("Profile")行(可以从Assets中获取,也可以使用带有UIImage的base64数据)。

HStack {
    Text("Apps")
        .font(.largeTitle)
        .fontWeight(.bold)
    
    Spacer()
    
    Image("Profile")
        .resizable()
        .scaledToFit()
        .frame(width: 40, height: 40)
        .clipShape(Circle())
}
.padding(.all, 30)

以下是该产品的结果: 显示大而粗的“应用程序”字样,然后是我的示例个人资料图片

使用NavigationView

假设您有一个NavigationView,其中只包含ScrollView.navigationTitle。您可以使用叠加层将个人资料图片添加到其中。

NavigationView {
    ScrollView {
        //your content here
    }
    .overlay(
        ProfileView()
            .padding(.trailing, 20)
            .offset(x: 0, y: -50)
    , alignment: .topTrailing)

    .navigationTitle(Text("Apps"))
}

ProfileView可能是这样的:

struct ProfileView: View {
    var body: some View {
        Image("Profile")
            .resizable()
            .scaledToFit()
            .frame(width: 40, height: 40)
            .clipShape(Circle())
    }
}

结果将会像这样...

上面代码的结果

...非常接近应用商店:

应用商店行为演示gif


谢谢您的回答。然而,这并不是我正在寻找的。现在滚动时,标题将仅从顶部边缘消失,而不会转换为带有内联标题的导航栏。有没有保留此功能的解决方案? - christophriepe
@christophriepe 哦,我的错。我编辑了我的答案以更好地适应您的需求。请看一下。 - Pyry Lahtinen
太棒了!基本上这正是我在寻找的。你有没有什么主意让图标像导航栏后面的标题一样消失,而不是渐隐? - christophriepe
3
更重要的是:个人资料图标需要成为一个按钮。然而,当添加填充和偏移量时,按钮不再可点击。如何解决这个问题? - christophriepe
1
只有当NavigationView的颜色为.clear时,它才能正常工作,这是默认值。不过这是一个很好的解决方案。谢谢! - multitudes
由于导航栏覆盖了图像,因此一旦使用叠加层,个人资料图标就无法点击。 - Rahul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接