In SwiftUI the view behind the tab bar in a TabView will shine through as if the backside of the tab bar was frosted glass. Apple uses this look all over the place in their own apps. But how do I add it to a view in SwiftUI?
Here's an example from the Podcasts app. The tab bar has the frosted glass effect. And so does the overlay mini player on top of the tab bar. Any tab bar in a TabView in will have this look by default, but not an associated overlay (the mini player in this case).
The Apple way
Investigating on the view hierarchy shows that Apple is using UIKit and UIVisualEffectViewfor this reason. You can define a VisualEffectView with just 5 lines of code:
struct VisualEffectView: UIViewRepresentable {
var effect: UIVisualEffect?
func makeUIView(context: UIViewRepresentableContext<Self>) -> UIVisualEffectView { UIVisualEffectView() }
func updateUIView(_ uiView: UIVisualEffectView, context: UIViewRepresentableContext<Self>) { uiView.effect = effect }
}
Usage Example:
struct ContentView: View {
var body: some View {
ZStack {
Image("BG")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
VisualEffectView(effect: UIBlurEffect(style: .dark))
.edgesIgnoringSafeArea(.all)
Text("Hello \nVisual Effect View")
.font(.largeTitle)
.fontWeight(.black)
.foregroundColor(.white)
}
}
}
The Native SwiftUI way:
You can add .blur() modifier on anything you need to be blurry like:
struct ContentView: View {
var body: some View {
ZStack {
Image("BG")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
.blur(radius: 20) // <- this is the important modifier. The rest is just for demo
Text("Hello \nSwiftUI Blur Effect")
.font(.largeTitle)
.fontWeight(.black)
.foregroundColor(.white)
}
}
}
Note the top and bottom of the view
Note that you can Group multiple views and blur them together.
iOS 15 - Apple Material
You can use iOS predefined materials with one line code:
.background(.ultraThinMaterial)
Related
Newbie at SwiftUI here. I am trying to show a dialog built in SwiftUI on top of an existing UIKit View. The idea is to be able to see the content of the UIKit view behind the SwiftUI dialog (like the default behaviour of an alert dialog box). But no what I try, I am unable to see the contents of the UIKit view. Is this even achievable?
I want an alert style dialog with background opacity adjusted somehow to see the contents of the UIKit view. Here is my output:
alert content hides the view behind it
Can somebody please point me in the right direction.
Here is my code sample:
The dialog in SwiftUI:
struct TestDialog: View {
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.black.opacity(0.5))
.frame(maxHeight: .infinity)
VStack(alignment: .center, spacing: 15) {
Text(.init("Some Text"))
.multilineTextAlignment(.center)
.padding()
Button(action: {}) {
Text("Button 1")
.padding(10)
}
Button(action: {}) {
Text("Button 2")
.padding(10)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.white))
.padding(40)
}
}
}
and the method called in my viewDidLoad():
func showTestDialog() {
let testView = TestDialog()
let testchildView = UIHostingController(rootView: testView)
addChild(testchildView)
let titleBarOffset: CGFloat = 11
testchildView.view.frame = view.frame.offsetBy(dx: 0, dy: -titleBarOffset)
view.addSubview(testchildView.view)
testchildView.didMove(toParent: self)
}
In order to see through the SwiftUI, you need to make sure it's host view has a transparent background:
testchildView.view.backgroundColor = .clear
This must be done at the host view level, since it is the parent/container.
This ought to be straightforward enough, but I cannot find out how to place a background behind a NavigationStack. With NavigationView, it was simply a matter of embedding in a ZStack with the background view called before the NavigationView (as described in an older post: How change background color if using NavigationView in SwiftUI?)
The same technique does not work for me with NavigationStack.
Here's what I have:
struct MyAngularGradient: View {
var body: some View {
ZStack {
AngularGradient(gradient: Gradient(colors: [.red, .orange , .yellow, .green, .cyan, .blue, .indigo, .purple, .red]), center: .leading)
AngularGradient(gradient: Gradient(colors: [.red, .orange , .yellow, .green, .cyan, .blue, .indigo, .purple, .red]), center: .leading)
.offset(x: -8)
}
.ignoresSafeArea()
}
}
var body: some View {
ZStack{
MyAngularGradient()
NavigationStack {
...
}
.navigationViewStyle(.stack)
} // end ZStack
FYI, I've used the same MyAngularGradient()in other apps (with NavigationView)
Any ideas? Thanks.
The closest I've managed to get so far is this:
struct MyView: View {
var body: some View {
NavigationStack {
ZStack {
Color.pink.ignoresSafeArea()
List {
NavigationLink("Hello") {
Text("Hello")
}
}
.navigationTitle("Title")
}
}
.scrollContentBackground(.hidden)
}
}
Which is somewhat dissatisfying for the following reasons:
When you scroll up the navigation bar appears, as expected, but ruins the effect imo.
I guess you can experiment changing this in a limited way using UINavigationBarAppearance() by updating it in the constructor of the view.
You can't apply a background to the whole app if you have multiple NavigationStackView based views in a TabView. (My example above was in a TabView)
When a new view is pushed on the stack the custom background disappears.
Only works on iOS 16+ due to the .scrollContentBackground(.hidden) modifier.
Does not work if you use ForEach to populate the List. Interestingly if you debug the hierarchy with and without the ForEach you'll see that SwiftUI adds a new view controller which is opaque in the case of ForEach.
This is probably a custom view but in the Reddit app there's a toolbar and the top left button(3 lines) opens this kind of view from the side that moves the current view to the right so you can only see about 25% of it and a new view that takes up about 75% of the screen slides in. Is there anything like this built into SwiftUI and if there isn't how would I go about implementing something like this?
This is my custom side bar behave similarly to what you just mentioned, you can try it. (Images and Code are below)
Before click:
After clicked:
struct ContentView: View {
#State var isClicked = false
var body: some View {
HStack {
Rectangle()
.fill(.orange)
.frame(width: isClicked ? UIScreen.main.bounds.width * 0.75 : 0)
VStack {
HStack {
Button {
withAnimation {
isClicked.toggle()
}
} label: {
Image(systemName: "menucard.fill")
.padding(.leading)
}
Spacer()
}
Spacer()
}
}
}
}
I want to place an MPVolumeView in my SwiftUI view but it doesn't behave like a normal SwiftUI view. I want the volume slider to be centered vertically between the two dividers. If you replaced VolumeSlider with Text it would be centered. How can I make the VolumeSlider behave in the same way?
// Must be run on real device, not simulator
import SwiftUI
import MediaPlayer
struct ContentView: View {
var body: some View {
VStack {
Divider()
VolumeSlider()
.frame(height: 128)
Divider()
}
}
}
struct VolumeSlider: UIViewRepresentable {
func makeUIView(context: Context) -> MPVolumeView {
MPVolumeView(frame: .zero)
}
func updateUIView(_ view: MPVolumeView, context: Context) {}
}
Firstly this behavior is in fact normal for a SwiftUI view. You are specifying a point height for the VolumeSlider. If you decrease the 128 it will not have the space between slider and bottom divider.
struct ContentView: View {
var body: some View {
VStack {
Divider()
VolumeSlider()
.frame(height: 50)
Divider()
}
}
}
I'll update this answer to try and make it not take up the whole space when .frame is removed.
Goal is to make a translucent sidebar on Mac Catalyst.
The code bellow gives a not translucent sidebar (image 1).
On Mac (not catalyst) the sidebar looks fine (image 2).
is it possible to have a translucent sidebar on Mac Catalyst?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
//sidebar
List {
Label("Books", systemImage: "book.closed")
Label("Tutorials", systemImage: "list.bullet.rectangle")
}
.background(Color.clear)
.listStyle(SidebarListStyle())
//content
Text("Sidebar")
.navigationTitle("Sidebar")
}
}
}
You should select "Optimize Interface for Mac" in your target's General settings tab. Then the sidebar will be translucent.
Start with the AppDelegate main and follow Apple's tutorial re: UISplitViewController "Apply a Translucent Background to Your Primary View Controller".
https://developer.apple.com/documentation/uikit/mac_catalyst/optimizing_your_ipad_app_for_mac
In wrapping UISplitViewController in a UIViewControllerRepresentable, I wasn't able to get translucency, but did get full-height sidebar.
I figured out that using .background(Color.clear) on sidebar View makes possible translucent background even if ListStyle is not specified as SidebarListStyle(). Works in Xcode 13.1 for me
struct ContentView: View {
var body: some View {
NavigationView { // without wrapping to NavigationView it won't work
List { // can be VStack or HStack
Text("Hello, world!")
.padding()
}
.listStyle(SidebarListStyle()) // works with other styles
Text("")
}
}
}
struct YourApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.toolbar {
Button {
} label: {
Image(systemName: "gear")
}
}
.background(Color.clear) // 3 <-- MUST HAVE!
}
}
}