Where should I set the environment values so that I can read it in a CustomPresentationDetent?

I want to implement a CustomPresentationDetent that has a different height depending on some external values. For a concrete example, let’s say I want my detent to be x points below the maxDetentValue, i.e.

struct MyDetent: CustomPresentationDetent {
    static func height(in context: Context) -> CGFloat? {
        context.maxDetentValue - x
    }
}

Basically, I want to pass this x from the outside.

CustomPresentationDetent.height(in:) is a static method so I cannot write an init that takes those values. Even if I could, I do not even get a chance to call the initialiser, since at the use site, I can only pass the meta type of my custom detent. That is, I must do .custom(MyDetent.self), and not .custom(MyDetent(x: 100)).

Then I thought of passing the values through the environment. The context parameter of height(in:) apparently can be used to access EnvironmentValues by using this dynamic member subscript.

However, the documentation of that subscript says something very confusing:

This uses the environment from where the sheet is shown, not the environment where the presentation modifier is applied.

Hence the question is:

Where exactly to put a .environment modifier so that I modify the environment of “where the sheet is shown”?

I tried adding .environment everywhere I can think of, passing each a different value, but I still see the default value in the height(in:) method. Here is a minimal reproducible example:

struct ContentView: View {
    var body: some View {
        SheetPresenter()
            .environment(.myValue, 1)
    }
}

struct SheetPresenter: View {
    @State private var shown = false
    
    var body: some View {
        Button("Show Sheet") {
            shown = true
        }
        .environment(.myValue, 2)
        .sheet(isPresented: $shown) {
            Text("Sheet")
                .environment(.myValue, 3)
                .presentationDetents([.custom(MyDetent.self)])
                .environment(.myValue, 4)
        }
        .environment(.myValue, 5)
    }
}

struct MyEnvKey: EnvironmentKey {
    static let defaultValue: Int = 0
}

extension EnvironmentValues {
    var myValue: Int {
        get { self[MyEnvKey.self] }
        set { self[MyEnvKey.self] = newValue }
    }
}

struct MyDetent: CustomPresentationDetent {
    static func height(in context: Context) -> CGFloat? {
        print(context.myValue)
        return 100
    }
}

On iOS 17.4, simulator, the print(context.myValue) statement always prints 0, the default value.

12

I am adding another answer that more directly addresses your question and code, but which at the same time shows the problems with your approach.

There were several issues with your code, as follows:

  • Your environment was set to use values of type Int, whereas a detent needs CGFloat
  • Regardless, they were never used because MyDetent was returning a constant of 100 of the correct type, CGFloat, instead of returning the actual context.myValue
  • You were setting the env value in many places, but you were never reading anywhere for actually passing it further to a view
  • This is a problem since it means you can’t manipulate the value
  • Even if you did read and adjusted the value, it wouldn’t change anything, because MyDetent would likely show the defaultValue
  • To fix this and actually use the adjusted values, you’d need to read it again in the SheetPresenter, BUT use the .height parameter instead, which makes using a custom detent pointless (not that it made sense to use one in the first place).

So the conclusion is that for what you’re trying to achieve, you need the .height parameter anyway, and some kind of global value. And unless you have a specific reason for that to exist and be passed via environment, it would make much more sense for it to be a property of an observable object instead.

import SwiftUI

struct SheetCustomDetentRootView: View {
    
    @Environment(.myValue) private var myValue: CGFloat // <- Here, read the myValue from the environment

    var body: some View {
        SheetPresenterView()
            .environment(.myValue, myValue)
            // .environment(.myValue, myValue + 350) // <- Here, uncomment this (and comment out the line above) to pass a custom value via environment
    }
}

struct SheetPresenterView: View {
    
    @Environment(.myValue) private var myValue: CGFloat // <- Here, read the myValue from the environment
    @State private var shown = false
    
    var body: some View {
        Button("Show Sheet") {
            shown = true
        }
        .sheet(isPresented: $shown) {
            Text("My value from environment is: (Int(myValue))")
                .presentationDetents([.custom(MyDetent.self)])
                // .presentationDetents([.height(myValue)]) // <- Here, uncomment this (and comment out the line above) to use the value passed via environment
        }
    }
}

struct MyEnvKey: EnvironmentKey {
    static let defaultValue: CGFloat = 100
}

extension EnvironmentValues {
    var myValue: CGFloat {
        get { self[MyEnvKey.self] }
        set { self[MyEnvKey.self] = newValue }
    }
}

struct MyDetent: CustomPresentationDetent {
    static func height(in context: Context) -> CGFloat? {
        print(context.myValue)
        return context.myValue
    }
}

#Preview {
    SheetCustomDetentRootView()
}

5

Because I can’t wrap my mind around the need for a custom detent or environment values gymnastics, here’s a simpler example that uses:

  • An observable class with a singleton to hold the max detent value
  • A view that accepts optional parameters for adjusting the sheet height relative to the max detent value
  • An internal state that allows further control if needed over the adjustment value

As such, you can:

  • Define a general max detent value in the singleton class
  • Update the max detent value as needed from anywhere (shown here as the textfield and stepper input of SomeTopView() )
  • Call the view that has the sheet with an adjustment value or without one (in which case the sheet will have the max detent value height)
  • Adjust the sheet height internally, if needed, in the same view as the sheet
  • Lose less hair due to not having to worry about environment values or custom detents.
import SwiftUI

struct SheetDetentView: View {
    
    //State values
    @State private var settings = SomeObserverClass.settings
    
    var body: some View {
        
        VStack {
            SomeTopView()
                .background(.background.secondary)
                .frame(height: 100)
            
            SheetPresenter(adjustHeight: 20) //or simply SheetPresenter() if no adjustment needed
        }
    }
}

struct SomeTopView: View {
    
    //Bindings
    @Bindable private var settings = SomeObserverClass.settings
    
    var body: some View {
        
        Form {
            Section("Top View"){
                HStack {
                    Text("Set max height")
                        .fixedSize()
                    TextField("Height", value: $settings.maxDetentValue, formatter: NumberFormatter() )
                        .multilineTextAlignment(.center)
                        .background(.background.secondary)
                    Stepper("", value: $settings.maxDetentValue, in: 100...450, step: 50)
                }
            }
        }
        
    }
}

struct SheetPresenter: View {

    //Parameters
    var adjustHeight: CGFloat? = 0
    
    //Bindings
    @Bindable private var settings = SomeObserverClass.settings

    //State values
    @State private var adjustValue: CGFloat = 0
    @State private var shown = true
    
    var body: some View {
        
        Form {
            Section("Bottom Sheet Presenter View") {
                
                Text("Max height is: (settings.maxDetentValue, format: .number.precision(.fractionLength(0)) )")
                
                //Inputs
                HStack {
                    Text("Adjust height")
                        .fixedSize()
                    TextField("Height", value: $adjustValue, formatter: NumberFormatter() )
                        .multilineTextAlignment(.center)
                        .background(.background.secondary)
                    Stepper("", value: $adjustValue, in: 0...settings.maxDetentValue, step: 10)
                }
                
                Button("Toggle Sheet") {
                    shown.toggle()
                }
                .sheet(isPresented: $shown) {
                    let maxHeight = settings.maxDetentValue
                    let adjustedHeight = maxHeight - adjustValue
                    
                    Text("Adjust parameter is: (adjustValue, format: .number.precision(.fractionLength(0)) )")
                    .foregroundStyle(.secondary)
                    
                    Text("Adjusted height is: (adjustedHeight, format: .number.precision(.fractionLength(0)) )")
                    
                    Text("((Int(adjustValue)) below max detent value of (Int(maxHeight)) )")
                        .foregroundStyle(.secondary)
                        .padding(.top)

                        .interactiveDismissDisabled()
                        .presentationDragIndicator(.hidden)
                        .presentationBackgroundInteraction(.enabled)
                        .presentationDetents([.height(adjustedHeight), .large])
                        
                }
            }
        }
        .onAppear {
            //if a parameter is passed to the view, set the state value to the parameter value
            if let value = adjustHeight {
                adjustValue = value
            }
        }
    }
}


@Observable
class SomeObserverClass {
    
    var maxDetentValue: CGFloat = 350
    
    static let settings = SomeObserverClass() //singleton
    
    private init() {}
}

#Preview {
    SheetDetentView()
}

If you run the code above, you’ll get the following, which allows you to play around with the values and notice how sheet height changes thanks to the observable singleton:

3

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật