Post

Replies

Boosts

Views

Activity

[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() } } }
3
1
490
Oct ’24