I recently started exploring the latest version of SwiftUI and came across a issue while working with TabView and NavigationStack. I downloaded Apple’s provided example code and began making changes to explore new SwiftUI changes. i added navigationtitle and toolbarBackground to first two tab. However, I noticed that the navigation bar "jumps" or resets when switching between tabs, even in their own example implementation.
Here’s a simplified version of the example code I was testing:
file name - WatchNowView
.navigationTitle("Watch Now")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(Color("AccentColor"),for: .navigationBar)
file name - LibraryView
.navigationTitle("Library")
.navigationBarTitleDisplayMode(.inline)
.toolbarBackground(Color("AccentColor"),for: .navigationBar)
Here is a sample code link (provided by Apple developer document) : destination-video
I have attached a gif below demonstrating this issue:
Questions:
Is this behavior expected in the latest version of SwiftUI, or is it a bug in the framework's handling of TabView and NavigationStack?
Is this behavior expected as all tabbar item have their own nativationStack?
Are there any official recommendations for maintaining seamless navigation experiences when using navigationStack and TabView?
This behavior detracts from the otherwise smooth experience SwiftUI aims to provide. If anyone has encountered this issue and found a workaround, I’d greatly appreciate your insights. I hope Apple can review this problem to enhance the usability of SwiftUI. Thank you!
SwiftUI
RSS for tagProvide views, controls, and layout structures for declaring your app's user interface using SwiftUI.
Post
Replies
Boosts
Views
Activity
I am currently developing my submission for the SSC and I noticed that the swift playgrounds app for iOS does not have support for the new swift ui features in iOS 18. Are we allowed to submit apps that use these iOS 18 only features?
Hi,
I am having some spacing issues with the new TabViewStyle.sidebarAdaptable. My app uses @ToolBarContentBuilder for navigationBar elements as there are some custom design requirements. One of these is title text that is set in the principal position.
Simplified example:
var body: some View {
Text("Body")
.toolbar {
toolbar()
}
}
@ToolbarContentBuilder
private func toolbar() -> some ToolbarContent {
ToolbarItem(placement: placement) {
Text("Title")
}
}
Everything with this setup works fine till I use an iPad with iOS 18 where the new toggleable sidebar is present. Upon switching to the sidebar layout the title does not move to the space adjacent to the navigation button (where the tab bar just was) and instead remains in its own bar, below the rest of the navigation.
I've noticed that when navigationTitle is set the the title set in toolbar() does appear in the right place.
var body: some View {
Text("Body")
.toolbar {
toolbar()
}
.navigationTitle("anything")
}
Is this expected behaviour? How can I achieve a single line nav bar with a title set this way?
Happy to provide a sandbox app to reproduce this issue.
Many thanks,
Matt
Hello,
In my app, I have an onboarding made of multiple steps in a NavigationStack.
I also have a state variable that controls an if else root branch to show either the onboarding NavigationStack or the app content if the onboarding is finished.
I noticed that when I end the onboarding (i.e. I switch to the other part of the if else root branch), the onAppear of the first View in the NavigationStack of the onboarding is called again. I don’t understand why.
Is this a bug?
Thanks,
Axel
enum Step {
case one
case two
case three
case four
}
struct ContentView: View {
@State private var isFinished: Bool = false
@State private var steps: [Step] = []
var body: some View {
if isFinished {
Button("Restart") {
steps = []
isFinished = false
}
} else {
NavigationStack(path: $steps) {
VStack {
Text("Start")
.onAppear { print("onAppear: start") }
Button("Go to step 1") { steps.append(.one) }
}
.navigationDestination(for: Step.self) { step in
switch step {
case .one:
Button("Go to step 2") { steps.append(.two) }
.onAppear { print("onAppear: step 1") }
case .two:
Button("Go to step 3") { steps.append(.three) }
.onAppear { print("onAppear: step 2") }
case .three:
Button("Go to step 4") { steps.append(.four) }
.onAppear { print("onAppear: step 3") }
case .four:
Button("End") {
isFinished = true
}
.onAppear { print("onAppear: end") }
}
}
}
.onAppear { print("onAppear: NavigationStack") }
}
}
}
I had write a widget after iOS 17+, It had a Toggle to perform a appintent .
When switch the toggle, the appintent will perfrom , just like
`
func perfrom() async throws -> some IntentResult {
// first
let first = try await getFristValue()
let second = try await getSecondValue(by: first)
let third = try awiat getThirdValue(by: second)
return .result()
}
`
and I found, it will work when I am debugging connect with Xcode.
But, when I don't connect xcode, it will not work.
How can I fixed it ?
When running a Mac Catalyst app that uses DocumentGroup, the app fails to display the document content. The document picker works as expected, but creating a new document or opening an existing one results in an empty window. This issue occurs regardless of whether “Optimize for Mac” or “Scale iPad” is selected.
Steps to Reproduce:
1. Download the sample project provided by Apple for building a document-based app in SwiftUI.
2. Delete the macOS version of the project.
3. Add a Mac Catalyst version of the app.
4. In the Mac Catalyst settings, select “Optimize for Mac” (the bug also appears if it is “Scale iPad”).
5. Run the project on macOS.
Expected Result:
The app should correctly display the content of the document when creating or opening it.
Actual Result:
The app opens an empty window when a new document is created or an existing one is opened.
Impact:
We have received multiple 1-star reviews, and our retention has dropped by two-thirds due to this issue.
Environment: Xcode 16.1; macOS 15.1 & 15.2 (on 15.0 it works fine)
Has anyone experienced the same issue? I filed multiple reports so far.
Before I updated to iOS 18 everything worked fine. I pushed out an update to my application on the App Store and I had no issues. After updating to the latest OS many of my touch events are no longer working and I have no idea why.
Sometimes when the app runs the touch events work fine and other times I can't click on half of my views & buttons. I am completely lost as to what might be happening.
I am having issues all over the application but let's focus on the navigation stack and the toolbar item buttons. I will post some code snippets, I have been unable to replicate this in a small playground project. This is my setup, I have two buttons but lets focus on the home & notifications view.
The custom Router
import SwiftUI
import Foundation
@Observable
class HomeRouter {
var navPath = NavigationPath()
@MainActor
func navigate(to destination: HOME_ROUTES) {
navPath.append(destination)
}
@MainActor
func navigateBack() {
navPath.removeLast()
}
@MainActor
func navigateToRoot() {
navPath.removeLast(navPath.count)
}
}
Home View
import os
import SwiftUI
import CoreLocation
import NotificationCenter
struct Home: View {
@State public var router: HomeRouter
@State private var showDetail = false
@State private var showMoreFields = false
@EnvironmentObject private var session: SessionStore
private var log = Logger(subsystem: "com.olympsis.client", category: "home_view")
init(router: HomeRouter = HomeRouter()) {
self._router = State(initialValue: router)
}
var body: some View {
NavigationStack(path: $router.navPath) {
ScrollView(.vertical) {
//MARK: - Welcome message
WelcomeCard()
.padding(.top, 25)
.environmentObject(session)
// MARK: - Announcements
AnnouncementsView()
.environmentObject(session)
// MARK: - Next Events
NextEvents()
.environmentObject(session)
// MARK: - Hot Events
HotEvents()
.environmentObject(session)
// MARK: - Nearby Venues
NearbyVenues()
.environmentObject(session)
Spacer(minLength: 100)
}
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Text("Olympsis")
.italic()
.font(.largeTitle)
.fontWeight(.black)
}
ToolbarItemGroup(placement: .topBarTrailing) {
Button(action: { router.navigate(to: .messages) }) {
ZStack(alignment: .topTrailing) {
Image(systemName: "bubble.left.and.bubble.right")
.foregroundStyle(Color.foreground)
if session.invitations.count > 0 {
NotificationCountView(value: $session.invitations.count)
}
}
}
Button(action: { router.navigate(to: .notifications) }) {
ZStack(alignment: .topTrailing) {
Image(systemName: "bell")
.foregroundStyle(Color.foreground)
if session.invitations.count > 0 {
NotificationCountView(value: $session.invitations.count)
}
}
}
}
}
.background(Color("background-color/primary"))
.navigationDestination(for: HOME_ROUTES.self, destination: { route in
switch route {
case .notifications:
NotificationsView()
.id(HOME_ROUTES.notifications)
.environment(router)
.environmentObject(session)
.navigationBarBackButtonHidden()
case .messages:
HomeMessagesView()
.id(HOME_ROUTES.messages)
.environment(router)
.environmentObject(session)
.navigationBarBackButtonHidden()
case .full_post_view(let id):
AsyncPostView(postId: id)
.id(HOME_ROUTES.full_post_view(id))
.environmentObject(session)
.navigationBarBackButtonHidden()
}
})
}
}
}
#Preview {
Home()
.environmentObject(SessionStore())
}
The Notifications View
import SwiftUI
struct NotificationsView: View {
@State private var notifications: [NotificationModel] = []
@Environment(HomeRouter.self) private var router
@EnvironmentObject private var session: SessionStore
var body: some View {
ScrollView {
if notifications.count > 0 {
ForEach(notifications, id: \.id){ note in
NotificationModelView(notification: note)
}
} else {
VStack {
Text("No new notifications")
HStack {
Spacer()
}
}.padding(.top, 50)
}
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .topBarLeading) {
Button(action:{ router.navigateBack() }) {
Image(systemName: "chevron.left")
.foregroundStyle(Color.foreground)
}
}
ToolbarItem(placement: .principal) {
Text("Notifications")
}
}
.task {
notifications = session.invitations.map({ i in
NotificationModel(id: UUID().uuidString, type: "invitation", invite: i, body: "")
})
}
}
}
#Preview {
NavigationStack {
NotificationsView()
.environment(HomeRouter())
.environmentObject(SessionStore())
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = self.parent.viewModel.messages[indexPath.row]
// var cell: RCBaseMessageCell? = tableView.dequeueReusableCell(withIdentifier: String( message.type.rawValue)) as? RCBaseMessageCell
var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
if #available(iOS 16.0, *) {
cell?.contentConfiguration = UIHostingConfiguration {
Text("123").onTapGesture {
print("123")
}
}
return cell
}
on ios 18.1 onTapGesture sometimes will not callback ,but ios 17 16 is ok,how can i fix it
Keep ScrollView position when adding items on the top using onAppear / onScrollTargeVisibilityChange
Related Post: Keep ScrollView position when adding items on the top
I was trying to implement a message view using ScrollView + LazyVStack. This view requires pagination so I used onScrollTargeVisibilityChange modifier to detect if the first item appears on screen and a new page needs to be loaded. I learnt from the above post that scrollPositon modifier can help keep the scroll position. I tested the method mentioned in that post -- use a button to add new items to the top -- and it worked.
However, when use onScrollTargeVisibilityChange modifier to add items, the view automatically scrolls to the top and cause the following loop:
first item in the list appears on screen --> load and insert more items to the top --> scroll view scrolls to top --> first item appears --> load more data --> scroll to top --> first item ... --> more data... --> top ...
Until it generates the error ScrollTargetVisibilityChange tried to update multiple times per frame.
Here is the simplified code.
struct Item: Identifiable {
var id: UUID = .init()
var content: String
}
struct ScrollViewTest: View {
@State private var items: [Item] = (0...30).map {Item(content:"\($0)")}.reversed()
@State private var itemID: Item.ID?
@State private var page: Int = 0
var body: some View {
ScrollView {
LazyVStack {
ForEach(items) {item in
Text(item.content)
.frame(height: 30)
}
}
.scrollTargetLayout()
}
.scrollPosition(id: $itemID)
.defaultScrollAnchor(.bottom)
.onScrollTargetVisibilityChange(idType: Item.ID.self) { onScreenItemIDs in
if onScreenItemIDs.first == items.first?.id {
page += 1
let newItems = (page*30+1 ... (page+1)*30).map {Item(content:"\($0)")}
items.insert(contentsOf: newItems.reversed(), at: 0)
}
}
.toolbar {
Button("Add") {
page += 1
let newItems = (page*30+1 ... (page+1)*30).map {Item(content:"\($0)")}
items.insert(contentsOf: newItems.reversed(), at: 0)
}
}
}
}
I want to load data while scrolling without the need of pressing any buttons. How can I solve this problem?
Our app occasionally crashes when creating a Live Activity using ActivityKit.
Crash log analysis indicates a wild pointer issue originating within ActivityKit.
The crash appears to be linked to the coexistence of multiple Live Activities.
Could this be a compatibility issue with ActivityKit?
We would appreciate any guidance or potential workarounds to resolve this issue.
Looking forward to your response.
Thread 0 Crashed:
0 ActivityKit 0x000000023023d034 0x230235000 + 32820 ( + 13792)
1 ActivityKit 0x000000023023d014 0x230235000 + 32788 ( + 13760)
2 Combine 0x00000001ac5bd168 0x1ac5b5000 + 33128 ( + 292)
3 Combine 0x00000001ac5c0658 0x1ac5b5000 + 46680 ( + 24)
4 Combine 0x00000001ac5d1714 0x1ac5b5000 + 116500 ( + 204)
5 Combine 0x00000001ac5c8da0 0x1ac5b5000 + 81312 ( + 24)
6 Combine 0x00000001ac5e8e98 0x1ac5b5000 + 212632 ( + 2520)
7 Combine 0x00000001ac5d1a4c 0x1ac5b5000 + 117324 ( + 24)
8 Combine 0x00000001ac68316c 0x1ac5b5000 + 844140 ( + 56)
9 Combine 0x00000001ac5d4a4c 0x1ac5b5000 + 129612 ( + 176)
10 Combine 0x00000001ac5bd43c 0x1ac5b5000 + 33852 ( + 392)
11 Combine 0x00000001ac5b7198 0x1ac5b5000 + 8600 ( + 24)
12 Combine 0x00000001ac5ead74 0x1ac5b5000 + 220532 ( + 712)
13 Combine 0x00000001ac5e2320 0x1ac5b5000 + 185120 ( + 24)
14 Combine 0x00000001ac5bfe74 0x1ac5b5000 + 44660 ( + 488)
15 Combine 0x00000001ac5b81b8 0x1ac5b5000 + 12728 ( + 24)
16 Combine 0x00000001ac5b8804 0x1ac5b5000 + 14340 (Just.receive(subscriber:) + 424)
17 Combine 0x00000001ac5f7a7c 0x1ac5b5000 + 273020 (Publishers.Merge.receive(subscriber:) + 820)
18 Combine 0x00000001ac5d9850 0x1ac5b5000 + 149584 (PublisherBox.receive(subscriber:) + 108)
19 Combine 0x00000001ac5b8154 0x1ac5b5000 + 12628 (AnyPublisher.receive(subscriber:) + 64)
20 Combine 0x00000001ac5dc9cc 0x1ac5b5000 + 162252 ( + 548)
21 Combine 0x00000001ac5dc764 0x1ac5b5000 + 161636 (Publishers.HandleEvents.receive(subscriber:) + 620)
22 Combine 0x00000001ac5d9850 0x1ac5b5000 + 149584 (PublisherBox.receive(subscriber:) + 108)
23 Combine 0x00000001ac5b8154 0x1ac5b5000 + 12628 (AnyPublisher.receive(subscriber:) + 64)
24 Combine 0x00000001ac5d92e8 0x1ac5b5000 + 148200 (Publishers.FlatMap.receive(subscriber:) + 416)
25 Combine 0x00000001ac5dc9cc 0x1ac5b5000 + 162252 ( + 548)
26 Combine 0x00000001ac5d9850 0x1ac5b5000 + 149584 (PublisherBox.receive(subscriber:) + 108)
27 Combine 0x00000001ac5b8154 0x1ac5b5000 + 12628 (AnyPublisher.receive(subscriber:) + 64)
28 Combine 0x00000001ac5f062c 0x1ac5b5000 + 243244 (Publishers.CompactMap.receive(subscriber:) + 572)
29 Combine 0x00000001ac5d9850 0x1ac5b5000 + 149584 (PublisherBox.receive(subscriber:) + 108)
30 Combine 0x00000001ac5b8154 0x1ac5b5000 + 12628 (AnyPublisher.receive(subscriber:) + 64)
31 Combine 0x00000001ac5e64e4 0x1ac5b5000 + 201956 (Publishers.SetFailureType.receive(subscriber:) + 552)
32 Combine 0x00000001ac5d92e8 0x1ac5b5000 + 148200 (Publishers.FlatMap.receive(subscriber:) + 416)
33 Combine 0x00000001ac5dc9cc 0x1ac5b5000 + 162252 ( + 548)
34 Combine 0x00000001ac5d9850 0x1ac5b5000 + 149584 (PublisherBox.receive(subscriber:) + 108)
35 Combine 0x00000001ac5b8154 0x1ac5b5000 + 12628 (AnyPublisher.receive(subscriber:) + 64)
36 Combine 0x00000001ac5f062c 0x1ac5b5000 + 243244 (Publishers.CompactMap.receive(subscriber:) + 572)
37 Combine 0x00000001ac5d9850 0x1ac5b5000 + 149584 (PublisherBox.receive(subscriber:) + 108)
38 Combine 0x00000001ac5b8154 0x1ac5b5000 + 12628 (AnyPublisher.receive(subscriber:) + 64)
39 Combine 0x00000001ac5bdd68 0x1ac5b5000 + 36200 (Publishers.ReceiveOn.receive(subscriber:) + 812)
40 Combine 0x00000001ac5f1e10 0x1ac5b5000 + 249360 (Publisher.sink(receiveCompletion:receiveValue:) + 304)
41 ActivityKit 0x00000002302594a0 0x230235000 + 148640 ( + 6064)
42 ActivityKit 0x0000000230258c18 0x230235000 + 146456 ( + 3880)
43 ActivityKit 0x0000000230258410 0x230235000 + 144400 ( + 1824)
44 ActivityKit 0x0000000230258124 0x230235000 + 143652 ( + 1076)
45 ActivityKit 0x0000000230258080 0x230235000 + 143488 ( + 912)
46 ActivityKit 0x000000023026d280 0x230235000 + 230016 ( + 4228)
47 ActivityKit 0x000000023026d39c 0x230235000 + 230300 ( + 4512)
48 libswiftDispatch.dylib 0x00000001ac59e7f4 0x1ac59d000 + 6132 ( + 28)
49 libswiftDispatch.dylib 0x00000001ac5a5a90 0x1ac59d000 + 35472 ( + 16)
50 libswiftDispatch.dylib 0x00000001ac59f97c 0x1ac59d000 + 10620 ( + 188)
51 libswiftDispatch.dylib 0x00000001ac59fa90 0x1ac59d000 + 10896 ( + 28)
52 libswiftDispatch.dylib 0x00000001ac59f5ec 0x1ac59d000 + 9708 ( + 28)
53 libdispatch.dylib 0x00000001ab37feac 0x1ab37c000 + 16044 ( + 20)
54 libdispatch.dylib 0x00000001ab38f428 0x1ab37c000 + 78888 ( + 56)
55 libswiftDispatch.dylib 0x00000001ac59ef38 0x1ac59d000 + 7992 ( + 180)
56 libswiftDispatch.dylib 0x00000001ac59e0dc 0x1ac59d000 + 4316 ( + 56)
57 libswiftDispatch.dylib 0x00000001ac59ec48 0x1ac59d000 + 7240 ( + 396)
58 libswiftDispatch.dylib 0x00000001ac59e188 0x1ac59d000 + 4488 (OS_dispatch_queue.sync(execute:) + 164)
59 ActivityKit 0x000000023026be70 0x230235000 + 224880 ( + 3228)
60 ActivityKit 0x000000023026b400 0x230235000 + 222208 ( + 556)
61 ActivityKit 0x00000002302d10b4 0x230235000 + 639156 ( + 25780)
62 ActivityKit 0x00000002302d0cd0 0x230235000 + 638160 ( + 24784)
63 ActivityKit 0x00000002302d0b94 0x230235000 + 637844 ( + 24468)
64 xxxx 0x0000000100919638 specialized static LiveActivityManager.startActivity(title:) + 169528 (LiveActivityManager.swift:96)
Pretty sure this is a no-no, but asking just in case there's an easy way to make this work
struct DocumentContentView: View {
private static let logger = Logger(
subsystem: "mySubsystem",
category: String(describing: Self.self)
)
var body: some View {
VStack {
Text("Hello")
logger.trace("hello")
}
}
}
This code generates the following compile error at the logger.trace line
buildExpression is unavailable: this expression does not conform to View
I suspect every line of the body var (or any @ViewBuilder - designated code?) needs to 'return' a View. Is this correct? or more importantly any work arounds other than putting some/all of the view contents in a. func()?
I have an observable object which is a model a view.
I also have a Tip (from TipKit) with @Parameter and a rule.
The source of truth for the state is in the observable object, however the Tip needs to be updated when state changes. So how do I bind between the two?
The way I was thinking was to sink updates from objectWillChange and check if Tips parameter needs to be updated or add didSet if it a @Published property. But I am not sure this is the best practice.
Schematic example:
class MyModel: ObservableObject {
struct TapSubmitTip: Tip {
@Parameter static var isSubmitButtonEnabled: Bool = false
var title: Text {
Text("Tap \"Submit\" to confirm your selection.")
}
var rules: [Rule] {
#Rule(Self.$isSubmitButtonEnabled) {
$0 == true
}
}
}
let tapSubmitTip = TapSubmitTip()
var objectWillChangeCancallable: AnyCancellable!
// Used by the view to enable or disable the button
var isSubmitButtonEnabled: Bool {
// Some logic to determine if button should be enabled.
}
init() {
objectWillChangeCancallable = objectWillChange.sink { [weak self] void in
guard let self else { return }
if isSubmitButtonEnabled {
TapSubmitTip.isSubmitButtonEnabled = true
}
}
}
...
// Call objectWillChange or update published properties as needed.
...
}
Currently, I am using multiple Texts in a horizontal stackview, to achieve animation of substring.
As you can see in the above animation, the text
- conversation
- meeting
- lecture
are animated.
However, there shortcoming of such an approach.
Text size is not consistent among different Text block. The following Text block are having different text size.
- Transform
- conversation/ meeting/ lecture
- to Quick Note
Any idea how we can achieve, so that all text blocks have same text size so that they appear like 1 sentence?
Or, how we can make the text blocks having constant text size, but able to perform line wrapping to next line, so that they appear like 1 sentence?
Currently, this is the code snippet I am using.
import SwiftUI
struct ContentView: View {
var array = ["lecture", "conversation", "meeting"]
@State var currentIndex : Int = 0
@State var firstString : String = ""
var body: some View {
VStack {
HStack {
Text("Transform")
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.title)
Text(firstString)
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.title)
.transition(AnyTransition.opacity.animation(.easeInOut(duration:1.0)))
.background(.yellow)
Text("to Quick Note")
.lineLimit(1)
.minimumScaleFactor(0.5)
.font(.title)
}.padding()
}
.animation(.default)
.onAppear {
firstString = array[0]
let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: true) { _ in
if currentIndex == array.count - 1 {
self.firstString = array[0]
currentIndex = 0
}
else {
self.firstString = array[currentIndex+1]
currentIndex += 1
}
}
}
}
}
#Preview {
ContentView()
}
Getting this error:
'Task' is only available in macOS 10.15 or newerSourceKit
LoggerForPreviews.swift(130, 9): Add 'if #available' version check
LoggerForPreviews.swift(129, 24): Add @available attribute to enclosing static method
LoggerForPreviews.swift(5, 20): Add @available attribute to enclosing actor
Does it have something to do with developing in VSCode?
import Foundation
import SwiftUI
// Logger: A concise, globally accessible logging utility for SwiftUI previews
public final actor PreviewLogger: Sendable {
// LogLevel: Defines severity levels for logging
public enum LogLevel: Int, Sendable, CaseIterable {
// Define cases based on LOG_LEVEL_MAP
case trace, debug, verbose, info, notice, warning, error, critical, fatal
// Computed property to get order based on case declaration
private var order: Int {
switch self {
case .trace: return 0
case .debug: return 1
case .verbose: return 2
case .info: return 3
case .notice: return 4
case .warning: return 5
case .error: return 6
case .critical: return 7
case .fatal: return 8
}
}
public var description: String {
// Use capitalized raw value for description
return String(describing: self).uppercased()
}
// Static function to compare log levels
static func >= (lhs: LogLevel, rhs: LogLevel) -> Bool {
return lhs.order >= rhs.order
}
}
// Shared instance for global access
public static let shared = PreviewLogger()
// Current log level
public var logLevelThreshold: LogLevel = .info
private init() {}
// Configure the logger's log level
public func configure(logLevelThreshold: LogLevel) {
self.logLevelThreshold = logLevelThreshold
}
// Helper function to center text within a given width
private func centered(_ text: String, in separator: String) -> String {
let totalLength = separator.count
let textLength = text.count
if textLength >= totalLength {
return text
}
let padding = (totalLength - textLength) / 2
let padString = String(repeating: " ", count: padding)
return padString + text
}
// Main logging function
public func log(
_ message: String,
level: LogLevel = .info,
file: String = #file,
function: String = #function,
line: Int = #line,
callerFile: String? = nil,
callerFunction: String? = nil,
callerLine: Int? = nil
) {
#if DEBUG
guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" else {
return
}
guard level >= logLevelThreshold else { return }
let fileName = (file as NSString).lastPathComponent
let cleanFunction = function.replacingOccurrences(of: "(_:file:function:line:)", with: "")
let levelIcon: String
switch level {
case .trace: levelIcon = "🔍"
case .debug: levelIcon = "🛠️"
case .verbose: levelIcon = "📝"
case .info: levelIcon = "ℹ️"
case .notice: levelIcon = "📢"
case .warning: levelIcon = "⚠️"
case .error: levelIcon = "❌"
case .critical: levelIcon = "🚨"
case .fatal: levelIcon = "💀"
}
let header = "[\(levelIcon) \(level.description)]"
let separator =
"· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·"
let finalSeparator =
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
let centeredHeader = centered(header, in: separator)
var output = """
\n\(separator)
\(centeredHeader)
"""
let locationInfo = "📍 \(fileName):\(line) ➜ \(cleanFunction)"
let centeredLocation = centered(locationInfo, in: separator)
output += "\n\(centeredLocation)"
if let callerFile = callerFile,
let callerLine = callerLine
{
let callerFileName = (callerFile as NSString).lastPathComponent
let callerInfo = "📱 Called from: \(callerFileName):\(callerLine)"
let centeredCallerInfo = centered(callerInfo, in: separator)
output += "\n\(centeredCallerInfo)"
}
output += """
\n\(separator)\n\(message)\n\(finalSeparator)
"""
print(output)
#endif
}
// Static Methods
public static func configure(logLevelThreshold: LogLevel) {
Task {
await shared.configure(logLevelThreshold: logLevelThreshold)
}
}
public static func log(
_ message: String,
level: LogLevel = .info,
file: String = #file,
function: String = #function,
line: Int = #line,
callerFile: String? = nil,
callerFunction: String? = nil,
callerLine: Int? = nil
) {
Task {
await shared.log(
message,
level: level,
file: file,
function: function,
line: line,
callerFile: callerFile,
callerFunction: callerFunction,
callerLine: callerLine
)
}
}
}
Hey everyone,
TL;DR
How do I enable a draggable TableView to drop Audio Files into Apple Music / Rekordbox / Finder?
Intro / skip me
I've been dabbling into Swift / SwiftUI for a few weeks now, after roughly a decade of web development.
So far I've been able to piece together many things, but this time I'm stuck for hours with no success using Forums / ChatGPT / Perplexity / Trial and Error.
The struggle
Sometimes the target doesn't accept the dropping at all
sometimes the file data is failed to be read
when the drop succeeds, then only as a stream in apple music
My lack of understanding / where exactly I'm stuck
I think the right way is to use UTType.fileUrl but this is not accepted by other applications.
I don't understand low-level aspects well enough to do things right.
The code
I'm just going to dump everything here, it includes failed / commented out attempts and might give you an Idea of what I'm trying to achieve.
//
// Tracks.swift
// Tuna Family
//
// Created by Jan Wirth on 12/12/24.
//
import SwiftySandboxFileAccess
import Files
import SwiftUI
import TunaApi
struct LegacyTracks: View {
@State var tracks: TunaApi.LoadTracksQuery.Data?
func fetchData() {
print("fetching data")
Network.shared.apollo.fetch(query: TunaApi.LoadTracksQuery()) { result in
switch result {
case .success(let graphQLResult):
self.tracks = graphQLResult.data
case .failure(let error):
print("Failure! Error: \(error)")
}
}
}
@State private var selection = Set<String>()
var body: some View {
Text("Tracks").onAppear{
fetchData()
}
if let tracks = tracks?.track {
Table(of: LoadTracksQuery.Data.Track.self, selection: $selection) {
TableColumn("Title", value: \.title)
} rows : {
ForEach(tracks) { track in
TableRow(track)
.draggable(track)
// .draggable((try? File(path: track.dropped_source?.path ?? "").url) ?? test_audio.url) // This causes a compile-time error
// .draggable(test_audio.url)
// .draggable(DraggableTrack(url: test_audio.url))
// .itemProvider {
// let provider = NSItemProvider()
// if let path = self.dropped_source?.path {
// if let f = try? File(path: path) {
// print("Transferring", f.url)
//
//
// }
// }
//
// provider.register(track)
// return provider
// } // This does not
}
}
.contextMenu(forSelectionType: String.self) { items in
// ...
Button("yoooo") {}
} primaryAction: { items in
print(items)
// This is executed when the row is double clicked
}
} else {
Text("Loading")
}
// }
}
}
//extension Files.File: Transferable {
// public static var transferRepresentation: some TransferRepresentation {
// FileRepresentation(exportedContentType: .audio) { url in
// SentTransferredFile( self.)
// }
// }
//}
struct DraggableTrack: Transferable {
var url: URL
public static var transferRepresentation: some TransferRepresentation {
FileRepresentation (exportedContentType: .fileURL) { item in
SentTransferredFile(test_audio.url, allowAccessingOriginalFile: true)
}
// FileRepresentation(contentType: .init(filenameExtension: "m4a")) {
// print("file", $0)
// print("Transferring fallback", test_audio.url)
// return SentTransferredFile(test_audio.url, allowAccessingOriginalFile: true)
// }
// importing: { received in
// // let copy = try Self.(source: received.file)
// return Self.init(url: received.file)
// }
// ProxyRepresentation(exporting: \.url.absoluteString)
}
}
extension LoadTracksQuery.Data.Track: @retroactive Identifiable {
}
import UniformTypeIdentifiers
extension LoadTracksQuery.Data.Track: @retroactive Transferable {
// static func getKind() -> UTType {
// var kind: UTType = UTType.item
// if let path = self.dropped_source?.path {
// if let f = try? File(path: path) {
// print("Transferring", f.url)
// if (f.extension == "m4a") {
// kind = UTType.mpeg4Audio
// }
// if (f.extension == "mp3") {
// kind = UTType.mp3
// }
// if (f.extension == "flac") {
// kind = UTType.flac
// }
// if (f.extension == "wav") {
// kind = UTType.wav
// }
//
// }
// }
// return kind
// }
public static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation {
$0.dropped_source?.path ?? ""
}
FileRepresentation(exportedContentType: .fileURL) { <#Transferable#> in
SentTransferredFile(<#T##file: URL##URL#>, allowAccessingOriginalFile: <#T##Bool#>)
}
// FileRepresentation(contentType: .fileURL) {
// print("file", $0)
// if let path = $0.dropped_source?.path {
// if let f = try? File(path: path) {
// print("Transferring", f.url)
// return SentTransferredFile(f.url, allowAccessingOriginalFile: true)
// }
// }
// print("Transferring fallback", test_audio.url)
// return SentTransferredFile(test_audio.url, allowAccessingOriginalFile: true)
// }
// importing: { received in
// // let copy = try Self.(source: received.file)
// return Self.init(_fieldData: received.file)
// }
// ProxyRepresentation(exporting: \.title)
}
}
On an iPad or iPhone running iPadOS / iOS 18.2 (built with Xcode 16.2), run the WritingApp sample code from https://developer.apple.com/documentation/SwiftUI/Building-a-document-based-app-with-SwiftUI from WWDC24
Then add the following struct to the project:
struct NavigationBarToolbar: ToolbarContent {
var body: some ToolbarContent {
ToolbarItem(placement: .secondaryAction) {
Button("Button 1", systemImage: "1.circle") { }
}
ToolbarItem(placement: .secondaryAction) {
Button("Button 2", systemImage: "2.circle") { }
}
ToolbarItem(placement: .secondaryAction) {
Button("Button 3", systemImage: "3.circle") { }
}
ToolbarItem(placement: .secondaryAction) {
Button("Button 4", systemImage: "4.circle") { }
}
ToolbarItem(placement: .secondaryAction) {
Button("Button 5", systemImage: "5.circle") { }
}
}
}
Comment out the original toolbar in the sample project and replace with:
.toolbar(content: NavigationBarToolbar.init)
Run the project and open or create a document
Click on the TitleMenu and choose Rename, in order to rename the file
Type in a new name and press Enter.
Notice how the items of the toolbar disappear
——
This issue has been submitted as feedback with number FB16100225
This issue is linked to the following feedbacks:
FB14855728
FB14855668
FB14849205
FB12343963
FB15164292
The WatchOS Control Center has an Edit/Done button at the bottom, and in its edit mode, elements can be moved around and added/removed.
Yet, the SwiftUI List doesn't have an edit mode on WatchOS.
My question is: is the edit functionality in Control Center a custom thing, or is that present in some SwiftUI component that I've missed?
Hi there, so I have this chart that's taking in a Date for it's x values and a time interval for their y values. For some reason, the labels aren't centering on each bar, the only fix I see is to add an offset to each label but that seems hacky.
My code:
Chart {
ForEach(weekBreakdownArr, id: \.startDate) { bd in
BarMark(
x: .value("Date", bd.startDate),
y: .value("Duration", bd.durationWorkDone),
width: .fixed(15)
)
.foregroundStyle(Color.redYarn)
.cornerRadius(2)
}
//...
}
// shownXValues are just the start dates in an array
.chartXAxis {
AxisMarks(position: .automatic, values: shownXValues) { val in
AxisValueLabel {
Text("Th")
.useAppFont(size: 12, relativeTo: .body, weight: .regular)
}
}
}
I made a ImagePicker which worked pretty well. But when app get bigger it stops. Does not react to change isPresented value. As far I know I changed nothing around this part of an App. Also same thing happened in different place, another kind of picker.
print ("HELL'o") never prints. Silence.
struct ImagePicker: View {
@Binding var imageSource: ImageSource
@State var showFileImporter: Bool = false
@EnvironmentObject var manager: Manager
var body: some View {
VStack {
....
Button(action: {
print("before", showFileImporter)
showFileImporter = true
print("after", showFileImporter)
},
label: { Text("open Image") })
.buttonStyle(.borderless)
.controlSize(.mini)
}.fileImporter(isPresented: $showFileImporter,
allowedContentTypes: [.png, .jpeg, .tiff],
onCompletion: { result in
print ("HELL'o") // Never prints
switch result {
case let .success(url):
guard let _ = try? Data(contentsOf: url) else { return }
....
case let .failure(error):
print(error)
}
})
}
}
Does anybody have an idea what happened?
I suspect some settings in completely different palce or bug or computer does not like me.
Dear Experts,
I created a SwiftUI View (in a UIKit based project) whose objective is to display a short animation in the front of the user screen. I developed it 8 month ago in XCode 15 for iOS 17, no problems.
But since iOS 18, I can observe huge lags on my animation only in Release app. The problem is not present in Debug app.
I don't understand why this problem occurs, the animation is quite simple, it's just an offset deplacement.
I tried many thing like:
Show the animation with a UINavigationController
Show the animation with a UIWindow
Move the view with .position
Remove the GeometryReader
All other animation
withAnimation and .animation
Task and DispatchQueue
Etc...
I found that the laggy animation occurs when I set the Optimization Level for the Swift Compiler - Code Generation to Optimize for Speed [-O]. That's very strange because we had this option on Release for iOS 17 and we had no lags...
I can share to you a Sample Repository with the configuration we have. https://github.com/Thibma/sample-animation-swiftui
Today the only option I used is to develop this feature in UIKit but it's too bad to skip the SwiftUI opportunity. :/
If you have any ideas to resolve this, I take !
Thank you !