While trying to control the following two scenes in 1 ImmersiveSpace, we found the following memory leak when we background the app while a stereoscopic video is playing.
ImmersiveView's two scenes:
Scene 1 has 1 toggle button
Scene 2 has same toggle button with a 180 degree skysphere playing a stereoscopic video
Attached are the files and images of the memory leak as captured in Xcode.
To replicate this memory leak, follow these steps:
Create a new visionOS app using Xcode template as illustrated below.
Configure the project to launch directly into an immersive space (set Preferred Default Scene Session Role to Immersive Space Application Session Role in Info.plist.
Replace all swift files with those you will find in the attached texts.
In ImmersiveView, replace the stereoscopic video to play with a large 3d 180 degree video of your own bundled in your project.
Launch the app in debug mode via Xcode and onto the AVP device or simulator
Display the memory use by pressing on keys command+7 and selecting Memory in order to view the live memory graph
Press on the first immersive space's button "Open ImmersiveView"
Press on the second immersive space's button "Show Immersive Video"
Background the app
When the app tray appears, foreground the app by selecting it
The first immersive space should appear
Repeat steps 7, 8, 9, and 10 multiple times
Observe the memory use going up, the graph should look similar to the below illustration.
In ImmersiveView, upon backgrounding the app, I do:
a reset method to clear the video's memory
dismiss of the Immersive Space containing the video (even though upon execution, visionOS raises the purple warning "Unable to dismiss an Immersive Space since none is opened". It appears visionOS dismisses any ImmersiveSpace upon backgrounding, which makes sense..)
Am I not releasing the memory correctly?
Or, is there really a memory leak issue in either SwiftUI's ImmersiveSpace or in AVFoundation's AVPlayer upon background of an app?
App file TestVideoLeakOneImmersiveView
First ImmersiveSpace file InitialImmersiveView
Second ImmersiveSpace File ImmersiveView
Skysphere Model File Immersive180VideoViewModel
File AppModel
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Posts under SwiftUI tag
187 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
Hello,
With iOS 18, when NavigationStack is in new TabView, with path parameter containing current navigation state is set, the navigation destination view is pushed twice.
See below with example that pushes twice on iOS 18 but is correct on iOS 17
@MainActor
class NavigationModel: ObservableObject {
static let shared = NavigationModel()
@Published var selectedTab: String
@Published var homePath: [Route]
@Published var testPath: [Route]
}
struct ContentView: View {
@StateObject private var navigationModel: NavigationModel = NavigationModel.shared
var body: some View {
TabView(selection: $navigationModel.selectedTab){
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag("home")
TestView()
.tabItem {
Label("Test", systemImage: "circle")
}
.tag("test")
}
}
}
struct HomeView: View {
@StateObject private var navigationModel: NavigationModel = NavigationModel.shared
var body: some View {
NavigationStack(path: $navigationModel.homePath){
VStack{
Text("home")
NavigationLink(value: Route.test1("test1")){
Text("Go to test1")
}
}
.navigationDestination(for: Route.self){ route in
NavigationModelBuilder.findFinalDestination(route:route)
}
}
}
}
I don't what causes the issue because it works well on iOS 16 and iOS 17. I think the path is somehow reset but I don't why (maybe by the TabView ?)
Note that the bug only occurs with TabView.
Don't really know if it is a TabView bug or if it is on my side.
I filed a feedback with sample project FB14312064
I have implemented a sample video editing timeline using SwiftUI and am facing issues. So I am breaking up the problem in chunks and posting issue each as a separate question. In the code below, I have a simple timeline using an HStack comprising of a left spacer, right spacer(represented as simple black color) and a trimmer UI in the middle. The trimmer resizes as the left and right handles are dragged. The left and right spacers also adjust in width as the trimmer handles are dragged.
Problem: I want to keep the background thumbnails (implemented currently as simple Rectangles filled in different colors) in the trimmer stationary as the trimmer resizes. Currently they move along as the trimmer resizes as seen in the gif below. How do I fix it?
import SwiftUI
struct SampleTimeline: View {
let viewWidth:CGFloat = 340 //Width of HStack container for Timeline
@State var frameWidth:CGFloat = 280 //Width of trimmer
var minWidth: CGFloat {
2*chevronWidth + 10
} //min Width of trimmer
@State private var leftViewWidth:CGFloat = 20
@State private var rightViewWidth:CGFloat = 20
var chevronWidth:CGFloat {
return 24
}
var body: some View {
HStack(spacing:0) {
Color.black
.frame(width: leftViewWidth)
.frame(height: 70)
HStack(spacing: 0) {
Image(systemName: "chevron.compact.left")
.frame(width: chevronWidth, height: 70)
.background(Color.blue)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged({ value in
leftViewWidth = max(leftViewWidth + value.translation.width, 0)
if leftViewWidth > viewWidth - minWidth - rightViewWidth {
leftViewWidth = viewWidth - minWidth - rightViewWidth
}
frameWidth = max(viewWidth - leftViewWidth - rightViewWidth, minWidth)
})
.onEnded { value in
}
)
Spacer()
Image(systemName: "chevron.compact.right")
.frame(width: chevronWidth, height: 70)
.background(Color.blue)
.gesture(
DragGesture(minimumDistance: 0)
.onChanged({ value in
rightViewWidth = max(rightViewWidth - value.translation.width, 0)
if rightViewWidth > viewWidth - minWidth - leftViewWidth {
rightViewWidth = viewWidth - minWidth - leftViewWidth
}
frameWidth = max(viewWidth - leftViewWidth - rightViewWidth, minWidth)
})
.onEnded { value in
}
)
}
.foregroundColor(.black)
.font(.title3.weight(.semibold))
.background {
HStack(spacing:0) {
Rectangle().fill(Color.red)
.frame(width: 70, height: 60)
Rectangle().fill(Color.cyan)
.frame(width: 70, height: 60)
Rectangle().fill(Color.orange)
.frame(width: 70, height: 60)
Rectangle().fill(Color.brown)
.frame(width: 70, height: 60)
Rectangle().fill(Color.purple)
.frame(width: 70, height: 60)
}
}
.frame(width: frameWidth)
.clipped()
Color.black
.frame(width: rightViewWidth)
.frame(height: 70)
}
.frame(width: viewWidth, alignment: .leading)
}
}
#Preview {
SampleTimeline()
}
When remove intermediate screen from path I expect the same behaviour like removing view controller from UINavigationController.viewControllers in UIKit.
But now it evoke pop animation and recreate associated StateObject.
It looks like instead of remove specific view controller in hierarchy, it removes the last one and change content on previous.
Code example to reproduce (Open Screen1, then Screen2 and touch Pop previous button):
enum Screen: String {
case screen1
case screen2
}
struct ContentView: View {
@State var path: [Screen] = []
var body: some View {
NavigationStack(path: $path) {
VStack {
Text("Root")
Button("open Screen 1") {
path.append(.screen1)
}
}
.navigationDestination(for: Screen.self) {
switch $0 {
case .screen1: ScreenView1(path: $path)
case .screen2: ScreenView2(path: $path)
}
}
}
}
}
struct ScreenView1: View {
@Binding var path: [Screen]
var body: some View {
VStack {
Text("Screen 1")
Button("Pop") { path.removeLast() }
Button("open Screen 2") { path.append(.screen2) }
}
}
}
struct ScreenView2: View {
@Binding var path: [Screen]
@StateObject var viewModel: Screen2ViewModel = .init()
var body: some View {
VStack {
Text("Screen 2")
Button("Pop") { path.removeLast() }
Button("Pop previous") {
// !!! Here the problem
path.remove(at: path.count - 2)
}
.disabled(path.count < 2)
}
}
}
class Screen2ViewModel: ObservableObject {
init() {
print("Screen2ViewModel.init")
}
}
Screencast:
It is possible to avoid pop animation by modifying path in transaction without animation:
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
path.remove(at: path.count - 2)
}
But my question is: it is possible to avoid screen associated StateObject recreating?
In visionOS, the virtual content is covered by the hand by default, so I want to know that in the hybrid space, if the distance of an entity is behind a real object, how can the object in the room be covered like the virtual content is covered by the hand?
I am trying to figure out how to set a button's label icon to match the button style when inside a list.
These four buttons display differently depending on context – if put inside a List or a VStack:
Button(role: .destructive) {} label: {
Label("Test", systemImage: "figure")
}.buttonStyle(.automatic)
Button(role: .destructive) {} label: {
Label("Test", systemImage: "figure")
}.buttonStyle(.bordered)
Button {} label: {
Label("Test", systemImage: "figure")
}.buttonStyle(.borderedProminent)
Button(role: .destructive) {} label: {
Label("Test", systemImage: "figure")
}.buttonStyle(.borderedProminent)
Inside a List, which looks weird in my opinion.
For reference, this is what they look like inside a VStack, which is what I'd expect:
I am not sure if this is intentional or a bug. If there are any workaround which do not explicitly set a specific color, like .foreground(.red), please let me know.
I have an NSMutableAttributedString that contains an NSMutableParagraphStyle, which sets the paragraphSpacing.
When I preview this in UIKit, everything displays correctly (paragraph spacing is applied). However, when I wrap this into an AttributedString, use it in a Text view (SwiftUI), the paragraphSpacing is ignored.
I looked into the documentation and noticed that AttributedString in SwiftUI does not yet support paragraphSpacing. For now, the only workaround I see is to keep this part in UIKit.
Has anyone found a way to make this work in SwiftUI?
Hello, everybody
I have a little problem I've finished learning swift and I've already know some SwiftUI, but I'm not really confident, also I had a break in my learning path. So I'm thinking about starting to learn SwiftUI one more time from zero, but I'm not sure where it can be better to do. My minimal iOS version - 17.
I was thinking about apple tutorials
https://developer.apple.com/tutorials/swiftui
https://developer.apple.com/tutorials/develop-in-swift
But is there any special order (of apple tutorials) or even other better tutorials? What can you recommend from your side?
Thank you in advance
I've found another interesting issue with the Focus system.
My text case is SwiftUI in Preview or Simulator (using an iPad mini case) -- I haven't tried this on other environments, so your mileage may vary.
I have a Form including several TextFields. Adding the usual @FocusState logic, I observe that if I type a TAB key, then
(a) If the TextFields are specified using TextField(“label”, text: $text), typing the tab key switches focus from field to field as expected, BUT
(b) If the TextFields are specified using TextField(“label”, text: $text, axis: .vertical), typing the tab key adds whitespace to the field and does NOT change focus.
In the latter case, I need to use an .onKeyPress(.tab) {….} modifier to achieve the desired result.
Reported as FB15432840
I use onPreferenceChange() and onScrollPhaseChange() to detect scroll is triggered by double tap. Is there any way to directly know the scroll is triggered by double tap?
I'm setting:
.immersionStyle(selection: .constant(.progressive(0.1...1.0, initialAmount: 0.1)), in: .progressive(0.1...1.0, initialAmount: 0.1))
In UnityVisionOSSettings.swift before build out in Xcode.
I'm having an issue where this only works on occasion. Seems random. I'll either get no immersion level available (crown dial is greyed out and no changes can be made) or it will only allow 0.5 - 1.0 immersion (dial will go below 0.5 but springs back to 0.5 when released).
With no changes to my setup or how I'm setting immersionStyle I've been able to get this to work as I would expect. Wondering if there is some bug that would be causing this to fail. I've tested a simple NativeSDK progressive immersion style with same code for custom setting and it works everytime, so it's something related to Unity.
Here is the entire UnityVisionOSSettings that, from as far as I can tell, are controlling this:
`// GENERATED BY BUILD
import Foundation
import SwiftUI
import PolySpatialRealityKit
import UnityFramework
let unityStartInBatchMode = false
extension UnityPolySpatialApp {
func initialWindowName() -> String { return "Unbounded" }
func getAllAvailableWindows() -> [String] { return ["Bounded-0.500x0.500x0.500", "Unbounded"] }
func getAvailableWindowsForMatch() -> [simd_float3] { return [] }
func displayProviderParameters() -> DisplayProviderParameters { return .init(
framebufferWidth: 1830,
framebufferHeight: 1600,
leftEyePose: .init(position: .init(x: 0, y: 0, z: 0),
rotation: .init(x: 0, y: 0, z: 0, w: 1)),
rightEyePose: .init(position: .init(x: 0, y: 0, z: 0),
rotation: .init(x: 0, y: 0, z: 0, w: 1)),
leftProjectionHalfAngles: .init(left: -1, right: 1, top: 1, bottom: -1),
rightProjectionHalfAngles: .init(left: -1, right: 1, top: 1, bottom: -1)
)
}
@SceneBuilder
var mainScenePart0: some Scene {
ImmersiveSpace(id: "Unbounded", for: UUID.self) { uuid in
PolySpatialContentViewWrapper(minSize: .init(1.000, 1.000, 1.000), maxSize: .init(1.000, 1.000, 1.000))
.environment(\.pslWindow, PolySpatialWindow(uuid.wrappedValue, "Unbounded", .init(1.000, 1.000, 1.000)))
.onImmersionChange() { oldContext, newContext in
PolySpatialWindowManagerAccess.onImmersionChange(oldContext.amount, newContext.amount)
}
KeyboardTextField().frame(width: 0, height: 0).modifier(LifeCycleHandlerModifier())
} defaultValue: { UUID() } .upperLimbVisibility(.automatic)
.immersionStyle(selection: .constant(.progressive(0.1...1.0, initialAmount: 0.1)), in: .progressive(0.1...1.0, initialAmount: 0.1))
WindowGroup(id: "Bounded-0.500x0.500x0.500", for: UUID.self) { uuid in
PolySpatialContentViewWrapper(minSize: .init(0.100, 0.100, 0.100), maxSize: .init(0.500, 0.500, 0.500))
.environment(\.pslWindow, PolySpatialWindow(uuid.wrappedValue, "Bounded-0.500x0.500x0.500", .init(0.500, 0.500, 0.500)))
KeyboardTextField().frame(width: 0, height: 0).modifier(LifeCycleHandlerModifier())
} defaultValue: { UUID() } .windowStyle(.volumetric).defaultSize(width: 0.500, height: 0.500, depth: 0.500, in: .meters).windowResizability(.contentSize) .upperLimbVisibility(.automatic) .volumeWorldAlignment(.gravityAligned)
}
@SceneBuilder
var mainScene: some Scene {
mainScenePart0
}
struct LifeCycleHandlerModifier: ViewModifier {
func body(content: Content) -> some View {
content
.onOpenURL(perform: { url in
UnityLibrary.instance?.setAbsoluteUrl(url.absoluteString)
})
}
}
}`
In this app, I set the preferredColorScheme in main
@main
struct TheApp: App {
@AppStorage("isDarkMode") private var isDarkMode = false
var body: some Scene {
WindowGroup {
ContentView()
.preferredColorScheme(isDarkMode ? .dark : .light)
}
}
}
isDarkMode is changed dynamically in code.
When code is compiled with Xcode 15.3, the background of ContentView changes from light to dark or vice versa.
When compiled with Xcode 16, no more change.
However, direct changes to objects do work, such as:
TextField("N", value: $t, format: .number)
.frame(width: 40)
.background(isDarkMode ? .gray : .white)
What has changed in Xcode 16 ? Is it documented somewhere ?
I saw this SO thread which describe similar problem.
https://www.reddit.com/r/swift/comments/upkprg/preferredcolorscheme_toggle_not_working/
Hi.
I am making my widgets ready for iOS 18 accented Home Screen widgets.
I am having issues setting the color of text to black.
This is required as I have a white button with black text and black icon.
I have tried manually, without using my Color, to set the text to .black or .white. It will always be white.
When applying the accent, I can use the .widgetAccentedRenderingMode(.fullColor) to turn the icon black, but I cannot do this for the text.
Button(intent: --intent--) {
HStack(alignment: .center) {
Text("Play")
.font(.system(size: 12, weight: .bold))
.foregroundStyle(Color("buttonAccentColor")) // Problem here, this is forced to be white, I want it to be black
Image("resume")
.renderingMode(.template)
.resizable()
.widgetAccentedRenderingMode(.fullColor) // This works
.aspectRatio(contentMode: .fit)
.frame(width: 16, height: 16)
.foregroundStyle(Color("buttonAccentColor"))
}
.padding(8)
.padding(.horizontal, 8)
}
.buttonStyle(.plain)
.background(Color("neutral100"))
.clipShape(Capsule())
Hi,
If Im in detailsOnly mode in SplitView how to manually open the SideBar as popover same as behavior as default rectangle menu icon at top of Details View.
Kind Regards
Hi,
Os to possible to customize the icon, color, positioning of the SplitView ToolBar that shows at details view, the menu blue icon. add buttons to it ? change its color, icon, size ? position like adding paddings ?
Kind Regards
I am working on an app that saves transaction entries. The user is requested to enter date / time, select local currency, payment type, category, description, and expense amount. When entering the description and entry amount they may tap the Done button above the keyboard to dismiss the keyboard. The problem is that quite commonly the Done button doesn't appear and with the decimal keyboard there is no way to dismiss the keyboard. I would say that the Done button appears only about 30-40% of the time.
In the EntryView structure, I define the @FocusStatus and pass it down to GetDescription(myFocus: myFocus) and GetMoney(myFocus: myFocus). Further down in EntryView under the toolbar I setting up the Done Button operation and resetting myFocus to nil.
In GetMoney I am using the binding var myFocus: FocusState<EntryView.Field?>.Binding.
Finally below the GetMoney TextField("Amount" ... there is the .focused(myFocus, equals: .ckAmt
GetDescription is not much of a problem because the default keyboard has a built in done button (.submitLabel(.done)).
I have Focus / Done operation in four other locations in my app where this works just fine. I need help figuring out why it fails part of the time.
struct EntryView: View {
enum Field: Hashable {
case ckDsc
case ckAmt
}
@FocusState private var myFocus: Field?
var body: some View {
GeometryReader { g in
VStack {
Form {
GetDate(entryDT: $entryDT)
GetCurrency()
GetPaymentType(g: g, entryPT: $entryPT)
GetCategory(entryCat: $entryCat)
GetDescription(g: g, entryDsc: $entryDsc, myFocus: $myFocus)
GetMoney(g: g, moneyD: $moneyD, myFocus: $myFocus)
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(.visible, for: .navigationBar)
.toolbarBackground(Color.orange, for: .navigationBar)
.toolbar(content: {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Done") {
myFocus = nil
}
}
ToolbarItem(placement: .principal) {
VStack (alignment: .leading) {
HStack {
Text("Expense Entry")
.font(.title)
.bold()
.kerning(1.5)
.padding(.leading, 10)
.frame(maxWidth: .infinity, alignment: .leading)
Spacer()
Button {
myFocus = nil
if moneyD != 0.0 {
self.saveButton() // button pressed
}
if !zeroEntry {
dismiss()
}
} label: {
Text("Save")
.bold()
.padding(.trailing, 10)
.tint(.blue)
}
.disabled(moneyD == 0.0)
}
}
}
})
}
}
// Entry save button has been pressed: save data to swiftdata
func saveButton() {
}
}
struct GetMoney: View {
var g: GeometryProxy
@Binding var moneyD: Double?
var myFocus: FocusState<EntryView.Field?>.Binding
var body: some View {
Section(header: Text("Enter Amount")) {
HStack {
TextField("Amount", value: $moneyD, format: .number.precision(.fractionLength(0...2)))
.focused(myFocus, equals: .ckAmt)
.font(.headline)
.padding(.vertical, 10)
.keyboardType(.decimalPad)
}
}
}
}
}
Hello everyone!
I want to add hand gesture controls to my Apple Watch app. Specifically, I’m looking to implement the following gestures:
1. Tapping index finger and thumb together once
2. Tapping index finger and thumb together twice
3. Fist clench
4. Double fist clench
Each gesture should trigger a different action within the app.
However, I can’t find any information on how to implement this. I only came across an example using .handGestureShortcut(.primaryAction) (Enabling the double-tap gesture on Apple Watch https://developer.apple.com/documentation/watchOS-Apps/enabling-double-tap), but I need to handle four distinct gestures.
Is this possible in watchOS 11? Any guidance or examples would be greatly appreciated!
Thank you!
Code that worked well before upgrading to Xcode 16 doesn't work if it builds on Xcode 16.
What doesn't work will not scroll when you use ScrollViewReader to scrollTo with View assigned id.
The bug is only reproduced on iOS 18 devices, and it works well on IOS 18 devices when built on Xcode 15 version.
Attached is the sample code below.
import SwiftUI
struct RegisterSheetView: View {
@State private var keyword = ""
@State private var descriptionMessage: String?
@State private var sheetHeight: CGFloat = .zero
@State private var sheetFirstPageHeight: CGFloat = .zero
@State private var sheetSecondPageHeight: CGFloat = .zero
@State private var sheetScrollProgress: CGFloat = .zero
var body: some View {
GeometryReader(content: { geometry in
let size = geometry.size
ScrollViewReader(content: { proxy in
ScrollView(.horizontal) {
HStack(alignment: .top, spacing: 0) {
FirstPage(size)
.id("First")
SecondPage(size)
.id("Second")
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
.scrollIndicators(.hidden)
.overlay(alignment: .topTrailing) {
Button(action: {
if sheetScrollProgress < 1 {
// First Page Button Action
withAnimation(.snappy) {
proxy.scrollTo("Second", anchor: .leading)
}
} else {
// Second Page Button Action
print("Register Button Action")
}
}, label: {
Text("continue")
.fontWeight(.semibold)
.opacity(1 - sheetScrollProgress)
.frame(width: 120 + (sheetScrollProgress * 15))
.overlay(content: {
Text("regist")
.fontWeight(.semibold)
.opacity(sheetScrollProgress)
})
.padding(.vertical, 12)
.foregroundStyle(.white)
.background(.linearGradient(colors: [.red, .orange], startPoint: .topLeading, endPoint: .bottomTrailing), in: .capsule)
})
.padding(15)
.offset(y: sheetHeight - 100)
.offset(y: sheetScrollProgress * -90)
}
})
})
.presentationCornerRadius(30)
.presentationDetents(sheetHeight == .zero ? [.medium] : [.height(sheetHeight)])
.onDisappear {
keyword = ""
}
}
// First View
@ViewBuilder
func FirstPage(_ size: CGSize) -> some View {
VStack(alignment: .leading, spacing: 12, content: {
Text("First Page")
.font(.largeTitle.bold())
.lineLimit(2)
Text(attributedSubTitle)
.font(.callout)
.foregroundStyle(.gray)
Text("What's the problem")
.foregroundStyle(.blue)
.font(.system(size: 16, weight: .semibold))
.frame(width: size.width, alignment: .leading)
.onTapGesture {
print("Tapped")
}
})
.padding(15)
.padding(.horizontal, 10)
.padding(.top, 15)
.padding(.bottom, 130)
.frame(width: size.width, alignment: .leading)
.heightChangePreference { height in
sheetFirstPageHeight = height
sheetHeight = height
}
}
var attributedSubTitle: AttributedString {
let string = "It works fine on Xcode 15 but builds on Xcode 16 and doesn't work on iOS 18 devices."
var attString = AttributedString(stringLiteral: string)
if let xcode16Range = attString.range(of: "Xcode 16") {
attString[xcode16Range].foregroundColor = .black
attString[xcode16Range].font = .callout.bold()
}
if let ios18Range = attString.range(of: "iOS 18") {
attString[ios18Range].foregroundColor = .black
attString[ios18Range].font = .callout.bold()
}
return attString
}
// Second View
@ViewBuilder
func SecondPage(_ size: CGSize) -> some View {
VStack(alignment: .leading, spacing: 12) {
Text("Key Registration")
.font(.largeTitle.bold())
CustomTextField(hint: "Please enter the contents", text: $keyword, icon: "person")
.padding(.top, 20)
}
.padding(15)
.padding(.horizontal, 10)
.padding(.top, 15)
.padding(.bottom, 190)
.overlay(alignment: .bottom, content: {
VStack(spacing: 15) {
Text("Apple **[Developer Forums](https://developer.apple.com/forums/)**")
.font(.caption)
.tint(.blue)
.foregroundStyle(.gray)
}
.padding(.bottom, 15)
.padding(.horizontal, 20)
.multilineTextAlignment(.center)
.frame(width: size.width)
})
.frame(width: size.width)
.heightChangePreference { height in
sheetSecondPageHeight = height
let diff = sheetSecondPageHeight - sheetFirstPageHeight
sheetHeight = sheetFirstPageHeight + (diff * sheetScrollProgress)
}
.minXChangePreference { minX in
let diff = sheetSecondPageHeight - sheetFirstPageHeight
let truncatedMinX = min(size.width - minX, size.width)
guard truncatedMinX > 0 else { return }
let progress = truncatedMinX / size.width
sheetScrollProgress = progress
sheetHeight = sheetFirstPageHeight + (diff * progress)
}
}
}
extension View {
@ViewBuilder
func heightChangePreference(completion: @escaping (CGFloat) -> ()) -> some View {
self
.overlay {
GeometryReader(content: { geometry in
Color.clear
.preference(key: SizeKey.self, value: geometry.size.height)
.onPreferenceChange(SizeKey.self) { value in
DispatchQueue.main.async {
completion(value)
}
}
})
}
}
@ViewBuilder
func minXChangePreference(completion: @escaping (CGFloat) -> ()) -> some View {
self
.overlay {
GeometryReader(content: { geometry in
Color.clear
.preference(key: OffsetKey.self, value: geometry.frame(in: .scrollView).minX)
.onPreferenceChange(OffsetKey.self) { value in
DispatchQueue.main.async {
completion(value)
}
}
})
}
}
}
struct SizeKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct OffsetKey: PreferenceKey {
static var defaultValue: CGFloat = .zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
struct CustomTextField: View {
var hint: String
@Binding var text: String
var icon: String
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextField(hint, text: $text)
.padding(.trailing, 28)
.padding(.top, 10)
Divider()
}
.overlay(alignment: .trailing) {
Image(systemName: icon)
.foregroundStyle(.gray)
}
}
}
struct ContentView: View {
@State private var isPresented: Bool = false
var body: some View {
Button {
isPresented.toggle()
} label: {
Text("Show Bottom Sheet")
.padding()
.background(Color.yellow)
}
.sheet(isPresented: $isPresented) {
RegisterSheetView()
}
}
}
I use onPreferenceChange() and onScrollPhaseChange() to detect the scroll is triggered by double tap.
Is there any way to directly know that the scroll is triggered by a double tap?
Sometimes when I am writing my code, previews blacks out and crashes and I get the warning PreviewShell crashed unexpectedly.
My Xcode has also been crashing a lot more after updating to Xcode 16. Does anyone have any idea why?