class NoPermissionsPromptViewFactory: ViewFactory {
func makePermissionsPromptView(call: Call?) -> some View {
EmptyView()
}
}
/// Then use your custom factory when creating the call view:
let viewFactory = NoPermissionsPromptViewFactory()
let callView = CallView(
viewFactory: viewFactory,
viewModel: viewModel
)
Permissions Prompt Customization
The permissions prompt view automatically appears when users join a call without the necessary camera or microphone permissions. Stream Video SDK allows you to fully customize or disable this component through the ViewFactory
protocol.
Default Behavior
By default, the SDK displays a prompt when:
- The user needs camera permission for video calls but hasn’t granted it
- The user needs microphone permission but hasn’t granted it
- The call’s capabilities require these permissions
The prompt appears as an overlay in the CallTopView
and provides a direct link to the app’s settings.
Disabling the Permissions Prompt
If you prefer to handle permissions differently or don’t want to show any prompt, you can disable it entirely:
Custom Styled Permissions Prompt
You can create a completely custom permissions prompt with your own design language:
struct CustomPermissionsPromptView: View {
@Injected(\.urlNavigator) private var urlNavigator
let call: Call?
@ObservedObject private var permissions = InjectedValues[\.permissions]
@State private var isHidden = false
private var requiresCameraPermission: Bool {
call?.state.ownCapabilities.contains(.sendVideo) ?? false
}
private var requiresMicrophonePermission: Bool {
call?.state.ownCapabilities.contains(.sendAudio) ?? false
}
private var isMissingCameraPermission: Bool {
requiresCameraPermission && !permissions.hasCameraPermission
}
private var isMissingMicrophonePermission: Bool {
requiresMicrophonePermission && !permissions.hasMicrophonePermission
}
var body: some View {
if (isMissingCameraPermission || isMissingMicrophonePermission) && !isHidden {
VStack(spacing: 12) {
Image(systemName: "exclamationmark.shield.fill")
.font(.largeTitle)
.foregroundColor(.orange)
Text(permissionText)
.font(.headline)
.multilineTextAlignment(.center)
HStack(spacing: 16) {
Button("Open Settings") {
try? urlNavigator.openSettings()
}
.buttonStyle(.borderedProminent)
Button("Dismiss") {
withAnimation {
isHidden = true
}
}
.buttonStyle(.bordered)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.fill(.ultraThinMaterial)
)
.transition(.move(edge: .top).combined(with: .opacity))
}
}
private var permissionText: String {
switch (isMissingCameraPermission, isMissingMicrophonePermission) {
case (true, true):
return "Camera and microphone access required"
case (true, false):
return "Camera access required"
case (false, true):
return "Microphone access required"
default:
return ""
}
}
}
class CustomPermissionsViewFactory: ViewFactory {
func makePermissionsPromptView(call: Call?) -> some View {
CustomPermissionsPromptView(call: call)
}
}
Best Practices
When customizing the permissions prompt, consider these best practices:
- User Experience: Keep the prompt non-intrusive but visible enough for users to notice
- Clear Messaging: Clearly explain why permissions are needed for the best call experience
- Easy Actions: Provide direct links to Settings so users can quickly grant permissions
- Dismissible: Allow users to dismiss the prompt if they choose to continue without permissions
- Persistence: Decide whether the prompt should reappear or stay dismissed for the session
Accessing Permission State
You can access the current permission state through the injected PermissionStore
:
@ObservedObject private var permissions = InjectedValues[\.permissions]
// Check individual permissions
let hasCameraAccess = permissions.hasCameraPermission
let hasMicAccess = permissions.hasMicrophonePermission
Related Components
ViewFactory
: The protocol for customizing UI components