[iOS 18] Scrolling behavior does not work only when built in Xcode 16

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 wasn't able to reproduce the issue on my end. Could you try testing again on the latest Xcode 16.1 Beta 3 and iOS 18.1 beta 6 (22B5069a) - https://developer.apple.com/download/applications/ ?

@DTS Engineer I used Sample Code above to run from Xcode 16.1 beta 3 to iOS 18.1 simulator, but the same issue is reproduced.

However, for iOS 17.x versions in the same environment, it works seamlessly and well.

I encountered the same problem

this Demo


var body: some View {

        ZStack {

            ScrollView {

                Text("preView ScrollView")

                    .frame(height: 1000)

                    .background(Color.blue)

            }

            VStack {

                Text(" ")

                    .frame(width: /*@START_MENU_TOKEN@*/100/*@END_MENU_TOKEN@*/, height: 100)

                    .contentShape(Rectangle())

                    .onTapGesture {

                        print("xxxxxx")

                    }

            }

        }

    }

[iOS 18] Scrolling behavior does not work only when built in Xcode 16
 
 
Q