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?
Explore the various UI frameworks available for building app interfaces. Discuss the use cases for different frameworks, share best practices, and get help with specific framework-related questions.
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hi,
I've encountered the following phenomenon when comparing drag&drop in iOS 18.0 with iOS 17.5:
Let's say you have a small child view sitting on top of a larger parent view.
In iOS 18.0, when hovering over the child view with a drag object and leaving the child view, the child view will propagate a
"dropInteraction:sessionDidExit:" call to the UIDropInteractionDelegate of its parent view.
The parent view now thinks, that the drag object is moved away from it (which is not the case!) and in turn its delegate doesn't receive any "dropInteraction:performDrop:" calls anymore.
In iOS 17.5 this case is handled correctly (without "dropInteraction:sessionDidExit:" being propagated wrongly by child views to their parent views).
Is this change in behavior intended or is this a bug?
Topic:
UI Frameworks
SubTopic:
UIKit
We have widgets in our app. We're now working on a Live Activitiy with a button calling an app intent. This app intent needs to update our Widgets, and we're seeing semi-great results.
When we're updating the widgets from within the app, it works great. Also from geofence triggers it usually works, so we're thinking it might have to do with the "widget update budget"?
According to the docs:
Cases in which WidgetKit doesn’t count reloads against your widget’s budget include when: The widget performs an app intent, such as when the user taps a button or toggles a switch.
But we're not really seeing that. When I run our app from within Xcode, everything runs great all the time and the widget gets updated within milliseconds, but when running the TestFlight version is more spotty.
To be clear: This is a button in a live activity, calling an app intent, and in turn, the app intent is calling reloadAllTimelines for our "regular" widgets.
The live activity itself always gets updated properly.
My question is basically, am I doing something wrong and can I do something to increase the consistency of the widget updating on time?
Abbreviated example:
final class UserEventIntent: NSObject, LiveActivityIntent {
@MainActor
func perform() async throws -> some IntentResult {
do {
let newStatus: (stat: Status, wasSame: Bool) = try await eventHelper.performEvent(status: status)
WidgetCenter.shared.reloadAllTimelines()
}catch {
await WidgetCenterBridge.updateLiveActivityForInProgress(false)
}
return .result()
}
在webview全屏打开Video视频前后获取设备的宽高结果不同 [UIScreen mainScreen].bounds对应的设备逻辑分辨率发生变化,导致根据分辨率显示的页面显示错误
Previously this code would trigger fine on pressesBegan in iOS 17 and earlier versions, but no longer works in iOS 18. How can I start capturing pressesBegan in iOS 18? It seems like UIResponder is just not capturing the keyboard anymore?
struct ContentView: View {
var body: some View {
KeyBoardView()
}
}
//To Use in SwiftUI
struct KeyBoardView: UIViewRepresentable{
func makeUIView(context: Context) -> KeyEventView {
KeyEventView()
}
func updateUIView(_ uiView: KeyEventView, context: Context) {
}
class KeyEventView: UIView {
init() {
super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?)
{
print("test")
}
}
}
Any CoreData experts know what could be going on here when trying to display to-many entity type relationship data?
On initialisation my model does an fetch request for an entity called Expense, and some prefetching for related entities (to-many type) like this: request.relationshipKeyPathsForPrefetching = ["splits"]
I was under the impression that calling a property on one of the related entities should fire the fault (if there is one) and give me the value. Here’s where it starts to go wrong. I have a property in my extension that I use in my detailed view:
public var viewExpenseSplits: [ExpenseSplit] {
return splits?.allObjects as? [ExpenseSplit] ?? []
}
and then a list in my detail view:
List(expense.viewExpenseSplits, id: \.self) { split in
Text(split.amount ?? "")
}
Result:
What's even more baffling is that I have no problem viewing these splits in the parent view. I thought perhaps I would have more luck passing the viewExpenseSplits through to the detail view instead of trying to access them from the Expense entity itself. Whilst I can print the data out when the view loads, my list still has the same problem.
I thought this would be trivial. Can anyone point to me where I'm going wrong?
Thanks!
When the home screen is in tinted mode in iOS 18, the widget gallery seems to display widgets with the background inset from the edge of the widget (i.e. negative padding).
You can see this behavior in the Apple News widget too, where the full-bleed content outside of the inset is very light. This only happens in the sample gallery, not when the widgets are placed on the home screen.
For my widgets, this outer edge contains important content and the clipping behavior makes the widgets look poorly designed when viewed in the gallery.
Is there any way to turn this behavior off and just show the widget normally in the gallery with no weird inset — the way it will actually display when added to the home screen?
If it matters, my widgets are currently configured with:
.contentMarginsDisabled()
.containerBackground(for: .widget) {
// ... (background color) */
}
Starting from iOS18 UIHostingController behaves weirdly that it resets the state of the view when you scroll up and down of the UITableView, even though associated UITableViewCell is unique and does reuse
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 1 {
let cell = tableView.dequeueReusableCell(withIdentifier: "SwiftUICell", for: indexPath)
let swiftUIView = SwiftUIView() // A SwiftUI View
cell.contentConfiguration = UIHostingConfiguration { // <- Here is the bug
swiftUIView
}
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "NormalCell", for: indexPath)
cell.textLabel?.text = items[indexPath.row]
return cell
}
}
Also in iOS18 release notes thee mentioned some fix related to this but looks like issue is not fixed
Please see the attached video
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hello everyone,
Recently, I have encountered an issue in my tvOS app where a specific property of UITextField, isSecureTextEntry, set to true, was preventing another property, keyboardType, from functioning correctly. In my case, keyboardType is set to numberPad option.
The problem is that during the first tap on the text field, the default keyboard with numbers, letters, and some special characters opens. However, after the second tap, the correct keyboard type with only numbers appears as I want. Removing isSecureTextEntry or setting to false solves the problem.
import UIKit
class ViewController: UIViewController {
private let textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.keyboardType = .numberPad
textField.isSecureTextEntry = true
view.addSubview(textField)
setupConstraints()
}
}
I am creating an application using PKCanvasview.
One function of that app is the ability to trace and retrieve a UITextView that has been addSubviewed to a PKCanvasView.
We have confirmed that this functionality works correctly in the simulator and on the actual device on IOS 17.4.
This feature did not work correctly on the IOS18 simulator and the actual device.
Is this a bug?
And if it is normal behavior, is there an alternative?
I am using PDFPageOverlayViewProvider from pdfkit. I would like to detect changes in the overlaid view and refresh PDFPageOverlayViewProvider. Currently, it does not refresh even when the overlaid view changes. Is there a way to refresh it?
Hi, I working with the new .subscriptionPromotionalOffer() modifier of SubscriptionStoreView, with the goal of presenting available promotional offers to users that have or have not yet subscribed to one of my subscriptions. In the former case, the promotional offers are rendered correctly along with the relevant subscription plan. But in the latter, if the user has already subscribed to the subscription, the promotional offers are not rendered at all, and the UI displays a grayed out "Current Plan" button. I expect the user to be able to enjoy this promotional offer, even it he's subscribed to the plan and he hasn't been offered the offer before.
Why is it not showing for this user? Should not existing promotional offers be available to existing subscribers?
struct SubscriptionsView: View {
var subscriptions: [String]
var offerId: String?
var body: some View {
SubscriptionStoreView(productIDs: subscriptions) {
// UI content
}
.subscriptionPromotionalOffer(
offer: { product, subscriptionInfo in
return subscriptionInfo.promotionalOffers.first(where: { $0.id == offerId })
},
signature: { product, subscriptionInfo, promotionalOffer in
return try await store.getSignature(product: product,subscriptionInfo: subscriptionInfo, promotionalOffer: promotionalOffer
)
})
}
}
For the last 2 version (18.2 beta 1 and 18.2. beta 2) I haven't been able to successfully update to either of them. I have been running Sequoia on an external drive for testing purposes and didn't have this problem with any of the 18.1 versions.
I'm currently on 18.1 (public) and when I download the update for 18.2 beta 2, the update appears to run (takes about 30 mins), preparing update, the restarts. When the Mac restarts, it just boots straight back to 18.1 without having applied the update.
Topic:
UI Frameworks
SubTopic:
General
Hello!
Is there any way to detect when the animation appearing in iOS 18 on switching tab items completes? This applies to TabView in SwiftUI.
Hello, Dear Developers
Problem Description
My team is working on functionality test in iOS18.
Basically, application functionality works as expected without any modification.
However, We found a small issue in settings application.
iOS 17
In iOS17, 3rd party OSS license string in settings application would been displayed if the license button is been clicked.
Model: iPhone SE 3
OS Version: 17.6.1
iOS 18
However, in iOS18, nothing but a blank page.
Model: iPhone SE 3
OS Version: 18.0.1
Settings.bundle
Below are files in settings.bundle.
What I've searched
Using XCode 16 makes no change
Nothing about settings.bundle in iOS 18 release note
A similar post: https://forums.developer.apple.com/forums/thread/764519
The solution in the post doesn't solve my problem.
If you know the solution, please let me know.
Best wishes.
I am building an app where tasks can be shown on multiple selected days. The user will select a day and the available tasks should be shown. I am struggling to build a view hierarchy where the visible tasks will update when added to the currently shown day.
A simplified model looks like the below:
@Model final class Day
{
private(set) var dayID: UUID = UUID()
var show: [Item]?
}
@Model final class Item
{
private(set) var itemID: UUID = UUID()
@Relationship(inverse: \Day.show) var showDays: [Day]?
}
I have a view representing a day, and I would like it to display a list of associated tasks below.
The view doesn't update if I list a day's tasks directly, e.g. List(day.show) {}
I get a compile error if I build a sub view that queries Item using the day's show array:
let show = day.show ?? []
let predicate = #Predicate<Item> { item in
show.contains(where: { $0.itemID == item.itemID }) }
I get runtime error if I flip the predicate:
let dayID = day.dayID
let predicate: Predicate<Item> = #Predicate<Item> { item in
item.showDays.flatMap { $0.contains(where: { $0.dayID == dayID }) } ?? false }
I'm at a loss of how to approach this, other that just forcing the whole view hierarchy to update every time a make a change.
Is there a recommended approach to this situation please?
I'm new to Swift and I got this error today. I have another SwiftUI page with the same code but the error doesn't appear there.
struct CardView: View {
var location: String
var description: String
var imageName: String
var body: some View {
VStack{
Image(imageName)
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 300, height: 200) // Set a fixed height for each card
.background(Color.black.opacity(0.7))
VStack(alignment: .leading) {
Spacer()
Text(location)
.padding(15)
.font(.headline)
.fontWeight(.bold)
.frame(width:300, alignment: .init(horizontal: .leading, vertical: .center))
.foregroundColor(.black)
.bold()
.background(Color.white)
.clipShape(RoundedCorner(radius: 10, corners: [.topLeft, .topRight]))
.padding([.leading, .trailing], -14)
.padding(.bottom, 10)
.offset(y: -213)
.offset(x: 28)
Text(description)
.font(.body)
.foregroundColor(.black)
.frame(width:300, alignment: .init(horizontal: .leading, vertical: .center))
.background(Color.white)
.clipShape(RoundedCorner(radius: 10, corners: [.bottomLeft, .bottomRight]))
.padding([.leading, .trailing], 14)
.padding(.bottom, 10)
.multilineTextAlignment(.leading)
.lineLimit(nil)
.offset(y: 2)
}
.padding()
.frame(width: 200)
.fixedSize()
}
.frame(height: 350) // Ensure each card has a fixed height
.background(Color.white)
.cornerRadius(10)
.shadow(radius: 5)
.padding([.leading, .trailing], 5) // Add padding to the sides for spacing
}
}
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
New in iOS 17.5 is UIImpactFeedbackGenerator/impactOccurred(at:), which generates haptic feedback at a specific screen location.
https://developer.apple.com/documentation/uikit/uiimpactfeedbackgenerator/4403143-impactoccurred
Which devices support this?
How does this work if the Taptic Engine has a fixed physical location?
Previously, i create an app and i'm using a userdefault with app group to enable to connect it with extension. But a bug causing me very frustating and so long to solve this, I think it just my code bug, but it was causing by the swiftui itself.
Where requestReview environment, causing my navigationlink that pointing to a view that include userdefault that connect to app group is freezing while tapping. Than it just caused in my ios 16 device, and work smoothly in my ios 18 device.
struct SettingView: View {
@Environment(\.requestReview) var requestReview
var body: some View {
NavigationStack {
List {
Section("Configuration") {
NavigationLink(destination: WidgetConfigurationView()) {
Label("Widget", systemImage: "paintpalette")
}
}
}
}
}
struct WidgetConfigurationView: View {
@Environment(\.dismiss) var dismiss
@AppStorage("widgetalignment", store: UserDefaults(suiteName: "group.com.my.app")) var alignment: Int = 0
}
can anyone explain why this happened? is this my mistake or the swiftui bug?
I've tried to animate custom UIViewRepresentable with SwitfUI animations, but it doesn't work. It just sets value without interpolation. What should i do to use interpolation values in UIKit views?
My example shows two "progress bars" red one is UIKit view, blue one is SwiftUI version. Sliders controls value directly, randomize button changes value to random with 5s animation.
When I press button SwiftUI progress bar animates exactly as it should, but UIKit's one just jumps to final position.
Set block of animatableData inside Animatable extension not called.
How can I use SwiftUI animation value interpolations for UIKit?
import SwiftUI
import UIKit
class UIAnimationView: UIView {
var progress: CGFloat = 0.5 {
didSet {
if self.progressConstraint != nil, self.innerView != nil {
self.removeConstraint(self.progressConstraint!)
}
let progressConstraint = NSLayoutConstraint(
item: innerView!,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: min(1.0, max(0.0001, progress)),
constant: 0
)
self.addConstraint(progressConstraint)
self.progressConstraint = progressConstraint
self.layoutIfNeeded()
}
}
var innerView: UIView?
private var progressConstraint: NSLayoutConstraint?
public override init(frame: CGRect) {
super.init(frame: frame)
self.performInit()
}
public required init?(coder: NSCoder) {
super.init(coder: coder)
self.performInit()
}
private func performInit() {
let innerView = UIView()
innerView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(innerView)
self.leadingAnchor.constraint(equalTo: innerView.leadingAnchor).isActive = true
self.topAnchor.constraint(equalTo: innerView.topAnchor).isActive = true
self.bottomAnchor.constraint(equalTo: innerView.bottomAnchor).isActive = true
let progressConstraint = NSLayoutConstraint(
item: innerView,
attribute: .trailing,
relatedBy: .equal,
toItem: self,
attribute: .trailing,
multiplier: progress,
constant: 0
)
self.progressConstraint = progressConstraint
self.addConstraint(progressConstraint)
self.innerView = innerView
self.innerView!.backgroundColor = UIColor.red
self.backgroundColor = UIColor.black
}
}
struct AnimationTest: UIViewRepresentable {
var progress: CGFloat
typealias UIViewType = UIAnimationView
func updateUIView(_ uiView: UIAnimationView, context: Context) {
print("progress: \(progress) \(context.transaction.isContinuous)")
uiView.progress = progress
}
func makeUIView(context: Context) -> UIAnimationView {
let view = UIAnimationView()
view.progress = progress
return view
}
}
extension AnimationTest: Animatable {
var animatableData: CGFloat {
get {
return progress
}
set {
print("Animation \(newValue)")
progress = newValue
}
}
}
struct AnimationDebug: View {
@State var progress: CGFloat = 0.75
var body: some View {
VStack {
AnimationTest(progress: progress)
Spacer()
VStack {
Slider(value: $progress, in: 0...1) {
Text("Progress")
}
}
GeometryReader { gr in
Color.blue
.frame(
width: gr.size.width * progress,
height: 48)
}
.frame(height: 48)
Button("Randomize") {
withAnimation(Animation.easeInOut(duration: 5)) {
progress = CGFloat.random(in: 0...1)
}
}
}
}
}
struct AnimationTest_Previews: PreviewProvider {
static var previews: some View {
AnimationDebug()
}
}