Reputation: 1036
I'm currently developing an application using SwiftUI.
I'm looking for some way to make a background color with opacity on a Sheet view.
is there any way to do that?
I've tried to do that with a code below, I can change the color with opacity property, but I can't see a text(Sheet) under the sheet View...
import SwiftUI
struct ContentView: View {
@State var isSheet = false
var body: some View {
Button(action: {self.isSheet.toggle()}) {
Text("Sheet")
}.sheet(isPresented: $isSheet){
Color.yellow.opacity(0.5)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Xcode: Version 11.7
Swift: Swift 5
Upvotes: 26
Views: 23086
Reputation: 121
iOS 16.4 onward
import SwiftUI
struct ContentView: View {
@State private var isSheet: Bool = false
var body: some View {
Button(action: {self.isSheet.toggle()}) {
Text("Sheet")
}.sheet(isPresented: $isSheet){
ZStack {
Text("Hello, World!")
}
.presentationBackground(.yellow.opacity(0.5))
}
}
}
#Preview {
ContentView()
}
Upvotes: 3
Reputation: 8407
Assperi don't want his answer to be edited, so posting it separately:
struct ContentView: View {
@SwiftUI.State var showSheet = false
@SwiftUI.State var appear: Bool = false
var body: some View {
Button(action: {self. showSheet.toggle()}) {
Text("Sheet")
}.sheet(isPresented: $showSheet){
Color.blue.opacity(0.5)
.background(ClearBackgroundView(appear: $appear))
.onAppear {DispatchQueue.main.async {appear = true}}
}
}
}
struct ClearBackgroundView: UIViewRepresentable {
@Binding var appear: Bool
class ClearView: UIView {
override func didMoveToSuperview() {
super.didMoveToSuperview()
clearBackgroundColor()
}
func clearBackgroundColor() {
var view: UIView? = self.superview?.superview
while let subview = view {
subview.backgroundColor = .clear
view = subview.superview
}
}
}
func makeUIView(context: Context) -> ClearView {
return ClearView()
}
func updateUIView(_ uiView: ClearView, context: Context) {
uiView.clearBackgroundColor()
}
}
Upvotes: 0
Reputation: 258393
You cannot do it with standard sheet official API (because every hosting controller view by default is opaque), so you can either create custom sheet view (with any features you needed) or use run-time workaround to find that view and set its background to clear. Like below (only for demo)
struct DemoView: View {
@State var isSheet = false
var body: some View {
Button(action: {self.isSheet.toggle()}) {
Text("Sheet")
}.sheet(isPresented: $isSheet){
Color.yellow.opacity(0.5)
.background(BackgroundClearView())
}
}
}
struct BackgroundClearView: UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
let view = UIView()
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .clear
}
return view
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
Upvotes: 44
Reputation: 5125
From iOS 16.4 it should be possible to use .presentationBackground(_ style: S) with colours and blurs (material types). If you want to make a transparent background just for one sheet:
struct ContentView: View {
@State private var isPresented = false
var body: some View {
Button(action: {
isPresented.toggle()
}, label: {
Label("Sheet", systemImage: "list.bullet.rectangle.portrait")
})
.sheet(isPresented: $isPresented) {
Text("Detail")
.presentationBackground(.clear)
}
}
}
Or you can use .presentationBackground() to inherit background style from presenter.
struct ContentView: View {
@State private var isPresented = false
var body: some View {
Button(action: {
isPresented.toggle()
}, label: {
Label("Sheet", systemImage: "list.bullet.rectangle.portrait")
})
.sheet(isPresented: $isPresented) {
Text("Detail")
.presentationBackground()
}
.backgroundStyle(Color("myThemeBackground"))
}
}
Upvotes: 24
Reputation: 1912
While the provided solutions do work, they will keep the background transparent for other sheets as well when they are swapped out.
So it is needed to restore the background color in dismantleUIView
like this:
struct TransparentBackground: UIViewRepresentable {
@MainActor
private static var backgroundColor: UIColor?
func makeUIView(context: Context) -> UIView {
let view = UIView()
Task {
Self.backgroundColor = view.superview?.superview?.backgroundColor
view.superview?.superview?.backgroundColor = .clear
}
return view
}
static func dismantleUIView(_ uiView: UIView, coordinator: ()) {
uiView.superview?.superview?.backgroundColor = Self.backgroundColor
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
Note that I used the new concurrency features in Swift.
Upvotes: 4
Reputation: 12237
For those who think relying on the order of messages in DispatchQueue is not a good idea (it's not really), here's a solution that relies on overriding didMoveToSuperview()
.
This is still not 100% future proof as we are modifying superview's superview and there's no guarantee it will be available at the moment our own view is added to the hierarchy, but I think this is still better than the DispatchQueue solution, since views are usually added from top to bottom, i.e. superview's superview will likely be available when didMoveToSuperview()
is called.
struct DemoView: View {
@State var isSheet = false
var body: some View {
Button(action: {self.isSheet.toggle()}) {
Text("Sheet")
}.sheet(isPresented: $isSheet){
Color.yellow.opacity(0.5)
.background(BackgroundClearView())
}
}
}
struct BackgroundClearView: UIViewRepresentable {
private class View: UIView {
override func didMoveToSuperview() {
super.didMoveToSuperview()
superview?.superview?.backgroundColor = .clear
}
}
func makeUIView(context: Context) -> UIView {
return View()
}
func updateUIView(_ uiView: UIView, context: Context) {}
}
Upvotes: 2
Reputation: 885
Put this in a Modal.swift
:
import SwiftUI
// View modifiers for modals: .sheet, .fullScreenCover
struct ModalColorView: UIViewRepresentable {
let color: UIColor
func makeUIView(context: Context) -> some UIView {
let view = UIView()
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = color
}
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {}
}
struct ModalColorViewModifier: ViewModifier {
let color: UIColor
func body(content: Content) -> some View {
content
.background(ModalColorView(color: color))
}
}
extension View {
/// Set transparent or custom color for a modal background (.screen, .fullScreenCover)
func modalColor(_ color: UIColor = .clear) -> some View {
self.modifier(ModalColorViewModifier(color: color))
}
}
Use like so:
.sheet(isPresented: $show) {
YourModalView(isPresented: $show)
// Zero args default is transparent (.clear)
//.modalColor()
// ..or specify a UIColor of choice.
.modalColor(UIColor(white: 0.2, alpha: 0.3))
}
}
Upvotes: 0
Reputation: 658
Using the AWESOME answer from @Asperi that I have been trying to find all day, I have built a simple view modifier that can now be applied inside a .sheet or .fullScreenCover modal view and provides a transparent background. You can then set the frame modifier for the content as needed to fit the screen without the user having to know the modal is not custom sized.
import SwiftUI
struct ClearBackgroundView: UIViewRepresentable {
func makeUIView(context: Context) -> some UIView {
let view = UIView()
DispatchQueue.main.async {
view.superview?.superview?.backgroundColor = .clear
}
return view
}
func updateUIView(_ uiView: UIViewType, context: Context) {
}
}
struct ClearBackgroundViewModifier: ViewModifier {
func body(content: Content) -> some View {
content
.background(ClearBackgroundView())
}
}
extension View {
func clearModalBackground()->some View {
self.modifier(ClearBackgroundViewModifier())
}
}
Usage:
.sheet(isPresented: $isPresented) {
ContentToDisplay()
.frame(width: 300, height: 400)
.clearModalBackground()
}
Upvotes: 36