Post

Replies

Boosts

Views

Activity

TabView's page style is broken on iOS 17.4
I have an infinite week scroller implemented using a TabView's page styling. basically when you scroll to the next week, it pre-loads the week after so that you can scroll infinitely. Since iOS 17.4, it seems to partially scroll two pages ahead. Scrolling backwards works fine. I made a radar: FB13718482 Here is a simplified implementation that has the issue reproduced. It uses the swift ordered collections library. Video of the issue: https://youtu.be/JW8dHqawURA import Foundation import OrderedCollections import SwiftUI struct ContentView: View { private let calendar: Calendar private let dateFormatter: DateFormatter @State var weeks: OrderedDictionary<String, WeekView.Week> @State var selectedWeek: WeekView.Week.ID init() { let calendar = Calendar.autoupdatingCurrent self.calendar = calendar let formatter = DateFormatter() formatter.calendar = calendar formatter.dateFormat = "MMM d" dateFormatter = formatter // Setup initial week let currentDate = Date() let weekIdentifier = Self.weekIdentifier(for: currentDate, calendar: calendar) let weeks: OrderedDictionary<WeekView.Week.ID, WeekView.Week> = [ weekIdentifier: Self.createWeek(for: currentDate, calendar: calendar) ] self._weeks = .init(initialValue: weeks) self._selectedWeek = .init(initialValue: weekIdentifier) } var body: some View { NavigationStack { TabView(selection: $selectedWeek) { ForEach(weeks.values) { week in WeekView(week: week) .tag(week.id) } } .onChange(of: selectedWeek, initial: true) { oldValue, newValue in createNextWeekIfRequired(for: weeks[newValue]!) } .tabViewStyle(.page(indexDisplayMode: .always)) .indexViewStyle(.page(backgroundDisplayMode: .always)) .navigationTitle(selectedWeek) } .environment(\.dateFormatter, dateFormatter) } private func createNextWeekIfRequired(for week: WeekView.Week) { guard let finalWeek = weeks.values.last, week.id == finalWeek.id, let day = finalWeek.days.first else { return } let nextWeek = calendar.date(byAdding: .weekOfYear, value: 1, to: day)! let identifier = Self.weekIdentifier(for: nextWeek, calendar: calendar) guard weeks[identifier] == nil else { return } weeks[identifier] = Self.createWeek(for: nextWeek, calendar: calendar) } static func weekIdentifier(for date: Date, calendar: Calendar) -> WeekView.Week.ID { let year = calendar.component(.yearForWeekOfYear, from: date) let week = calendar.component(.weekOfYear, from: date) return "\(year)-\(week)" } static func createWeek(for date: Date, calendar: Calendar) -> WeekView.Week { let startOfDay = calendar.startOfDay(for: date) let weekOfYear = calendar.component(.weekOfYear, from: startOfDay) let startOfWeek = calendar.nextDate( after: startOfDay + 1, matching: .init(hour: 0, minute: 0, second:0, nanosecond: 0, weekday: 1, weekOfYear: weekOfYear), matchingPolicy: .nextTime, direction: .backward )! var dates: [Date] = [] calendar.enumerateDates( startingAfter: startOfWeek - 1, matching: .init(hour: 0, minute: 0, second:0, nanosecond: 0), matchingPolicy: .nextTime ) { result, exactMatch, stop in guard let result, calendar.component(.weekOfYear, from: result) == weekOfYear else { stop = true return } dates.append(result) } return WeekView.Week(id: weekIdentifier(for: date, calendar: calendar), days: dates) } } #Preview { ContentView() } import SwiftUI struct WeekView: View { struct Week: Identifiable { var id: String var days: [Date] } var week: Week private let columnDefinition = [GridItem]( repeating: GridItem(.flexible(minimum: 10, maximum: 200), alignment: .center), count: 7 ) var body: some View { LazyVGrid(columns: columnDefinition, alignment: .center) { ForEach(week.days, id: \.timeIntervalSinceReferenceDate) { date in DayView(date: date) } } .frame(maxWidth: .infinity) } } import SwiftUI struct DayView: View { @Environment(\.dateFormatter) private var dateFormatter let date: Date var body: some View { VStack { Text(date, formatter: dateFormatter) Image(systemName: "calendar") .foregroundStyle(Color.blue) } } } #Preview { DayView(date: Date()) } import Foundation import SwiftUI struct DateFormatterEnvironmentKey: EnvironmentKey { static var defaultValue: DateFormatter = { let formatter = DateFormatter() formatter.calendar = .autoupdatingCurrent formatter.dateFormat = "MMM d" return formatter }() } extension EnvironmentValues { var dateFormatter: DateFormatter { get { self[DateFormatterEnvironmentKey.self] } set { self[DateFormatterEnvironmentKey.self] = newValue } } }
1
0
356
Apr ’24
Unable to install swift-format in Xcode Cloud
here is my post clone scrip: #!/bin/zsh brew install swift-format And here is the errors im getting in the Xcode Cloud logs: Showing All Messages cd /Volumes/workspace/repository/ci_scripts && /bin/zsh /Volumes/workspace/repository/ci_scripts/ci_post_clone.sh ==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:02180ca8b8295422ae84921bcf034b7ee8ce5575488bd5e6a37a192e53cd5d34 #=#=# ##O#-# ### 5.5% ##################################################### 74.3% ######################################################################## 100.0% ==> Pouring portable-ruby-3.1.4.el_capitan.bottle.tar.gz Running `brew update --auto-update`... To restore the stashed changes to /Users/local/Homebrew, run: cd /Users/local/Homebrew && git stash pop ==> Homebrew collects anonymous analytics. Read the analytics documentation (and how to opt-out) here: https://docs.brew.sh/Analytics No analytics have been recorded yet (nor will be during this `brew` run). ==> Homebrew is run entirely by unpaid volunteers. Please consider donating: https://github.com/Homebrew/brew#donations Error: swift-format: the bottle needs the Apple Command Line Tools to be installed. You can install them, if desired, with: xcode-select --install If you're feeling brave, you can try to install from source with: brew install --build-from-source swift-format It is expected behaviour that most formulae will fail to build from source. It is expected behaviour that Homebrew will be buggy and slow when building from source. Do not create any issues about failures building from source on Homebrew's GitHub repositories. Do not create any issues building from source even if you think this message is unrelated. Any opened issues will be immediately closed without response. Do not ask for help from Homebrew or its maintainers on social media. You may ask for help building from source in Homebrew's discussions but are unlikely to receive a response. If building from source fails, try to figure out the problem yourself and submit a fix as a pull request. We will review it but may or may not accept it. Run command: 'cd /Volumes/workspace/repository/ci_scripts && /bin/zsh /Volumes/workspace/repository/ci_scripts/ci_post_clone.sh' ==> Downloading https://ghcr.io/v2/homebrew/portable-ruby/portable-ruby/blobs/sha256:02180ca8b8295422ae84921bcf034b7ee8ce5575488bd5e6a37a192e53cd5d34 #=#=# ##O#-# ### 5.5% ##################################################### 74.3% ######################################################################## 100.0% ==> Pouring portable-ruby-3.1.4.el_capitan.bottle.tar.gz Running `brew update --auto-update`... To restore the stashed changes to /Users/local/Homebrew, run: cd /Users/local/Homebrew && git stash pop ==> Homebrew collects anonymous analytics. Read the analytics documentation (and how to opt-out) here: https://docs.brew.sh/Analytics No analytics have been recorded yet (nor will be during this `brew` run). ==> Homebrew is run entirely by unpaid volunteers. Please consider donating: https://github.com/Homebrew/brew#donations Error: swift-format: the bottle needs the Apple Command Line Tools to be installed. You can install them, if desired, with: xcode-select --install If you're feeling brave, you can try to install from source with: brew install --build-from-source swift-format It is expected behaviour that most formulae will fail to build from source. It is expected behaviour that Homebrew will be buggy and slow when building from source. Do not create any issues about failures building from source on Homebrew's GitHub repositories. Do not create any issues building from source even if you think this message is unrelated. Any opened issues will be immediately closed without response. Do not ask for help from Homebrew or its maintainers on social media. You may ask for help building from source in Homebrew's discussions but are unlikely to receive a response. If building from source fails, try to figure out the problem yourself and submit a fix as a pull request. We will review it but may or may not accept it. Command exited with non-zero exit-code: 1 The ci_post_clone.sh is not executable and was run using zsh (default shell on macOS). To make sure your script runs correctly, make the file executable using `chmod +x` and add an appropriate shebang line. I feel like Xcode Cloud can't possibly be missing Xcode command line tools. I also can't imagine they forgot to run chmod +x on the default run script files Any Ideas here?
0
0
814
Feb ’24
TextEditor with a fixedSize and scroll disabled is completely broken
I am trying to build a text editor that shrinks to its content size. The closest I have been able to get has been to add the .scrollDisabled(true) and .fixedSize(horizontal: false, vertical: true) modifiers. This almost achieves what I need. There are two problems though: long single line text gets cut off at the end creating line breaks causes the text editor to grow vertically as expected (uncovering the cut off text in point 1 above). However, when you delete the line breaks, the TextEditor does not shrink again. I have had a radar open for some time: FB13292506. Hopefully opening a thread here will get more visibility. And here is some sample code to easily reproduce the issue: import SwiftUI struct ContentView: View { @State var text = "[This is some long text that will be cut off at the end of the text editor]" var body: some View { TextEditor(text: $text) .scrollDisabled(true) .fixedSize(horizontal: false, vertical: true) } } #Preview { ContentView() } Here is a gif of the behavior:
1
1
919
Feb ’24
Is there any way to speed up SwiftUI Previews?
We have a fairly large app with dozens of dependencies. SwiftUI Previews take ages to build, even though they are only using a fraction of our code. I managed to speed them up a bit by disabling most of my custom build phases. But they are still super slow due to how long it takes to build things like Firebase. Anyone have any ideas here? Is there a way to ensure that previews are only compiling the files that they need to work?
1
0
2.3k
Oct ’23
UINavigationController has a delay rendering a UIHostingController's .navigationBarTitle(), .searchable(), and .toolbar()
It seems UINavigationControllers do not play nicely with UIHostingController. When presenting a UIHostingController, the UINavigationBar does not update with the nav bar title until after the SwiftUI view has finished appearing on screen. This only happens the first time the hosting controller loads its view. If you were to store the hosting controller somewhere, then try to present it a second time, everything works as expected. Feedback: FB13287789 Pretty easy to reproduce: Scene Delegate: import UIKit import SwiftUI class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } window = UIWindow(windowScene: windowScene) let navigationController = UINavigationController() let contentView = RootView(navController: navigationController) { navController in navController.pushViewController(UIHostingController(rootView: SearchView()), animated: true) } let firstController = UIHostingController(rootView: contentView) navigationController.setViewControllers([firstController], animated: false) window?.rootViewController = navigationController window?.makeKeyAndVisible() } func sceneDidDisconnect(_ scene: UIScene) {} func sceneDidBecomeActive(_ scene: UIScene) {} func sceneWillResignActive(_ scene: UIScene) {} func sceneWillEnterForeground(_ scene: UIScene) {} func sceneDidEnterBackground(_ scene: UIScene) {} } SwiftUI Views: import UIKit import SwiftUI struct RootView: View { var navController: UINavigationController var didTapButton: (UINavigationController) -> () var body: some View { Button { didTapButton(navController) } label: { Text("Tap this to show broken search bar animation") } .navigationTitle("First Page") .navigationBarTitleDisplayMode(.inline) } } struct SearchView: View { let items: [Int] = { (0...100).map {$0} }() @State var searchText: String = "" var body: some View { List { ForEach(items, id: \.self) { item in Text("\(item)") } } .listStyle(.plain) .navigationTitle("Search Page") .navigationBarTitleDisplayMode(.inline) .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search") } }
1
1
1.4k
Oct ’23
AsyncStream(unfolding:) cancels early
So I have been trying to figure out how to solve an issue all day today. Basically, I have two async streams and I am using async-algorithms combineLatest to merge them, and then .map to select a value from one of the streams. Unfortunately, there is no "AnyAsyncSequence" so I can't type erase. As such, I have wrapped this sequence in an AsyncStream. I noticed that by doing this, the observer of this stream seems to cancel early. When in reality it should only be ending when my class deinitializes. Here is the code that does not work: var values: AsyncStream<MyValue> { let stream = combineLatest(self.myFirstStream, self.mySecondStream) .map(self.selectValueToReturn) return AsyncStream { for await value in stream { // For some reason, only a few values get returned here and then the stream gets cancelled. return value } return nil } } Strangely enough, if I switch to a continuation based AsyncStream. It works fine: var values: AsyncStream<FeatureConfig> { return AsyncStream { continuation in Task { [weak self] in guard let self else { return } let stream = combineLatest(self.myFirstStream, self.mySecondStream) .map(self.selectValueToReturn) for await value in stream { continuation.yield(value) } continuation.finish() } } }
1
0
755
Mar ’23
plugin targets missing from swift-format dependency
So I've added swift-format as one of my dependencies to my own swift package so that I can use their lint swift package plugin. Here is my Package.swift: // swift-tools-version: 5.7 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "EssentialFeed", platforms: [ .iOS(.v16), .macOS(.v13), .macCatalyst(.v16), .tvOS(.v16), .watchOS(.v9) ], products: [ // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "EssentialFeed", targets: ["EssentialFeed"]), .library( name: "EssentialFeedTestHelpers", targets: ["EssentialFeedTestHelpers"]), ], dependencies: [ .package(url: "https://github.com/apple/swift-format", .upToNextMajor(from: "0.50700.1")), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "EssentialFeed", dependencies: [], plugins: [ .plugin(name: "LintPlugin", package: "swift-format"), .plugin(name: "FormatPlugin", package: "swift-format"), ] ), .target( name: "EssentialFeedTestHelpers", dependencies: []), .testTarget( name: "EssentialFeedTests", dependencies: ["EssentialFeed", "EssentialFeedTestHelpers"]), .testTarget( name: "EssentialFeedAPIEndToEndTests", dependencies: ["EssentialFeed", "EssentialFeedTestHelpers"]), ] ) I get the following error when pulling this package: product 'LintPlugin' required by package 'essentialfeed' target 'EssentialFeed' not found in package 'swift-format'. But if you look at swift-format's Package.swift, you can see the plugins exist.
1
0
1.1k
Feb ’23
Command CompileAssetCatalog failed with a nonzero exit code
I keep getting the following error when trying to build my app: 2022-07-08 19:59:03.837 ibtoold[65032:257043] DEBUG: Added to environment: {     TMPDIR = "/var/folders/n1/mn972l7d7t53rk8kghkt9b4h0000gp/T/61CA0AE1-7540-4D8C-94B2-3BF37B7EB31C"; } /* com.apple.actool.errors */ : error: Failed to launch AssetCatalogSimulatorAgent via CoreSimulator spawn     Failure Reason: Failed to spawn AssetCatalogSimulatorAgent on IBSimDeviceTypeiPad2x (3E230588-1FA3-45F9-AB2E-C02394631AA7, (null), Shutdown)     Underlying Errors:         Description: Invalid device: The specified device is not available for spawning processes. This is happening with Xcode 13.4.1 This started after installing the Xcode 14 beta along side Xcode 13.4.1 (I never actually opened or used the beta before this started happening). Things I've tried: deleting DerrivedData changing the DerrivedData directory uninstalling Xcode 13 and 14 beta, and only reinstalling Xcode 13. restarting my computer. Anyone else see this? Also a side note: I can't seem to add iOS 15 simulators after this started happening. I wonder if its related. EDIT: Upon further digging im seeing this error message in my CoreSimulator.log file: Jul 8 20:06:10 bmifsud-mbp CoreSimulatorService[2918] <Notice>: Could not locate default .GlobalPreferences.plist for iOS 15.5 (15.5 - 19F70) - com.apple.CoreSimulator.SimRuntime.iOS-15-5 Is there any way to reset this preference?
5
0
9.5k
Jul ’22
State loops in UIViewRepresentable
I have been working with UIViewRepresentable a lot recently and noticed something that seems to be quite the flaw: There doesn't seem to be a clean way of passing data back to your swiftUI views in a performant way. Here is an example: struct MapWrapper: UIViewRepresentable { &#9;&#9;@Binding var centerCoordinate: CLLocationCoordinate2D &#9;&#9;init(centerCoordinate: Binding<CLLocationCoordinate2D>) { &#9;&#9;&#9;&#9;self._centerCoordinate = centerCoordinate &#9;&#9;} &#9;&#9;func makeUIView(context: Context) -> MKMapView { &#9;&#9;&#9;&#9;var mapView = MKMapView() &#9;&#9;&#9;&#9;mapView.delegate = context.coordinator &#9;&#9;&#9;&#9;return mapView &#9;&#9;} &#9;&#9; &#9;&#9;func updateUIView(uiView: MKMapView, context: Context) { &#9;&#9;&#9;&#9;// Updating the maps center coordinate triggers mapViewDidChangeVisibleRegion, which thus causes updateUIView to trigger again. &#9;&#9;&#9;&#9;uiView.centerCoordinate = centerCoordinate &#9;&#9;} &#9;&#9;func makeCoordinator() -> Coordinator { &#9;&#9;&#9;&#9;return Coordinator(parent: self) &#9;&#9;} &#9;&#9;class Coordinator: MKMapViewDelegate { &#9;&#9;&#9;&#9;var parent: MapWrapper &#9;&#9;&#9;&#9;init(parent: MapWrapper) { &#9;&#9;&#9;&#9;&#9;&#9;self.parent = parent &#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;func mapViewDidChangeVisibleRegion(_ mapView: MKMapView) { &#9;&#9;&#9;&#9;&#9;&#9;// updating this variable causes a view reload, thus calling updateUIView, and eventually causing this delegate method to trigger again. &#9;&#9;&#9;&#9;&#9;&#9;parent.centerCoordinate = mapView.centerCoordinate } } As you can see from the above code, dragging the map anywhere would cause a loop. The only workaround I have found for this is to give your coordinator a "shouldUpdateState" Boolean variable. and set that prior to updating your bindings. Has this ever been addressed by apple anywhere? Or are we just expected to only modify our view state from the outside of a UIViewRepresentable?
3
1
3.3k
Jan ’21
Widget's showing up completely blank on iOS 14 simulator
I've been following the part 1 tutorial until the point where they deploy the widgets to the simulator. I noticed that my widget was completely blank. Checked other widgets and noticed that the apple news widgets on the simulator is like this as well. Heres the code: // //  EmojiRangerWidget.swift //  EmojiRangerWidget // //  Created by Brent Mifsud on 2020-06-24. //  Copyright © 2020 Apple. All rights reserved. // import WidgetKit import SwiftUI import Intents struct Provider: IntentTimelineProvider {     public func snapshot(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (SimpleEntry) -> ()) { let entry = SimpleEntry(date: Date(), character: .panda)         completion(entry)     }     public func timeline(for configuration: ConfigurationIntent, with context: Context, completion: @escaping (Timeline&lt;Entry&gt;) -> ()) { var entries: [SimpleEntry] = [ SimpleEntry(date: Date(), character: .panda) ]         // Generate a timeline consisting of five entries an hour apart, starting from the current date.         let currentDate = Date()         for hourOffset in 0 ..< 5 {             let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)! let entry = SimpleEntry(date: entryDate, character: .panda)             entries.append(entry)         }         let timeline = Timeline(entries: entries, policy: .atEnd)         completion(timeline)     } } struct SimpleEntry: TimelineEntry {     public let date: Date let character: CharacterDetail } struct PlaceholderView : View {     var body: some View {         Text("Placeholder View")     } } struct EmojiRangerWidgetEntryView : View {     var entry: Provider.Entry     var body: some View { AvatarView(entry.character)     } } @main struct EmojiRangerWidget: Widget {     private let kind: String = "EmojiRangerWidget"     public var body: some WidgetConfiguration {         IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider(), placeholder: PlaceholderView()) { entry in             EmojiRangerWidgetEntryView(entry: entry)         }         .configurationDisplayName("Emoji Ranger Detail")         .description("Keep track of your favorite emoji ranger.")     } } struct EmojiRangerWidget_Previews: PreviewProvider { static var previews: some View { AvatarView(.panda) .previewContext(WidgetPreviewContext(family: .systemSmall)) } }
6
0
5.4k
Jun ’20