I'd like to create a custom SwiftUI view that supports extracting its title string along with the localization comment into a string catalog. Like the SwiftUI Text view does. I have a view with an init similar to the localization init of Text. But it looks like I'm missing something obvious.
Two questions:
How do I get the actual localized string using a LocalizedStringKey?
Why is the comment not picked up and added to the string catalog?
// 1) My custom view with localization support:
// I'd like to build a view which supports extraction of strings into a string catalog like the SwiftUI `Text` view does.
struct MyLocalizableView: View {
private var localizedTitle: String
init (_ titleKey: LocalizedStringKey, table: String? = nil, bundle: Bundle? = nil, comment: StaticString? = nil) {
// PROBLEM I:
// The following line does not work. I is a fantasy call. It depicts my idea how I would expect it to work.
// My question is: How do I get the actual localized string using a `LocalizedStringKey`?
self.localizedTitle = String(localizedKey: titleKey, table: table, bundle: bundle, comment: comment)
}
var body: some View {
// At this point I want to do an operation on an actual string and not on a LocalizedStringKey. So I can't just pass the LocalizedStringKey value along.
// Do `isEmpty` or some other operation on an actual string:
if localizedTitle.isEmpty {
Text("Show one thing")
} else {
Text("Show another thing")
Text("** \(localizedTitle) **")
}
}
}
// 2) The call site:
struct ContentView: View {
var body: some View {
// PROBLEM II: "My title key" is picked up and is extracted into the string catalog of the app. But the comment is NOT!
MyLocalizableView("My title key", comment: "The title of the view...")
.padding()
}
}
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 am getting this crash specific to iOS 18.4. This has happened only on iPad, combined with 18.4. I do not what is causing the crash. I am unable to reproduce the crash. Crash report attached at the bottom of the post.
Thank you.
first.crash
second.crash
I'm struggling with an elusive issue, where selecting a TextField, which then shows the onscreen keyboard, causes a later application hang, but only on an actual device (not in preview, not in the simulator). I've narrowed my code down to the simplest repro.
If you run the following code, you can try both the repro case and a case that avoids the issue, where the only difference between the two is whether you select a TextField, which displays the onscreen keyboard. Even if you just dismiss the keyboard, the hang still happens.
To repro, select the "Press Here To Show Keyboard To Cause Hang" TextField, then select the "Press Here Before Showing Keyboard To Not Hang" link, then follow through with Create New Group, Add Member, Create New Member. The app will hang when you select Create New Member.
If you start with the "Press Here Before Showing Keyboard to Not Hang", everything works. You can even select the Group Name TextField before selecting Add Member without issue.
I'm looking for any ideas or suggestions, thanks.
Here's the code:
import SwiftUI
class Member : Identifiable {
let id = UUID()
var name = ""
}
class Group : Identifiable {
let id = UUID()
var name = ""
var members = [Member]()
var memberIds = [UUID]()
}
struct MemberView : View {
@Environment(\.dismiss) private var dismiss
@Binding private var groups: [Group]
@Binding private var members: [Member]
@State private var member: Member
init(groups: Binding<[Group]>, members: Binding<[Member]>, member: Member? = nil) {
_groups = groups
_members = members
_member = .init(wrappedValue: member ?? Member())
}
var body: some View {
Form {
Section(header: Text("Member Data")) {
TextField("Member Name", text: $member.name)
}
}
}
}
struct MembersView : View {
@Environment(\.dismiss) private var dismiss
@Binding private var groups: [Group]
@Binding private var members: [Member]
init(groups: Binding<[Group]>, members: Binding<[Member]>) {
_groups = groups
_members = members
}
var body: some View {
if members.isEmpty {
NavigationLink("Create New Member") {
MemberView(groups: $groups, members: $members)
}
}
else {
List(members) { member in
Text(member.name)
}
}
}
}
struct GroupView : View {
@Binding private var groups: [Group]
@Binding private var members: [Member]
@State private var group = Group()
@State private var selectedMemberId: UUID?
init(groups: Binding<[Group]>, members: Binding<[Member]>) {
_groups = groups
_members = members
}
var body: some View {
Form {
Section(header: Text("Group Data")) {
TextField("Group Name", text: $group.name)
}
Section(header: Text("Members")) {
List(members) { member in
if group.memberIds.contains(member.id) {
NavigationLink {
MemberView(groups: $groups, members: $members, member: member)
}
label: {
Text(member.name)
}
}
}
NavigationLink {
MembersView(groups: $groups, members: $members)
}
label: {
Text("Add Member")
}
}
}
}
}
struct GroupsView : View {
@Binding private var groups: [Group]
@Binding private var members: [Member]
init(groups: Binding<[Group]>, members: Binding<[Member]>) {
_groups = groups
_members = members
}
var body: some View {
if groups.isEmpty {
NavigationLink("Create New Group") {
GroupView(groups: $groups, members: $members)
}
}
else {
List(groups) { group in
Text(group.name)
}
}
}
}
struct MainView : View {
@State private var groups: [Group]
@State private var members: [Member]
@State private var settings: [String]
@State private var setting = ""
init() {
_groups = .init(wrappedValue: [])
_members = .init(wrappedValue: [])
_settings = .init(wrappedValue: [])
}
var body: some View {
NavigationStack {
if settings.isEmpty {
VStack {
TextField("Press Here To Show Keyboard To Cause Hang (Whether Or Not You Type Anything)", text: $setting) {
settings.append("Hang")
}
Button("Press Here Before Showing Keyboard To Not Hang") {
settings.append("No Hang")
}
}
}
else {
GroupsView(groups: $groups, members: $members)
}
}
}
}
#Preview {
MainView()
}
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
MainView()
}
}
}
Topic:
UI Frameworks
SubTopic:
SwiftUI
I'm implementing a Map with user location customization in SwiftUI using iOS 17+ MapKit APIs. When using the selection parameter with Map, the default blue dot user location becomes tappable but shows an empty annotation view. However, using UserAnnotation makes the location marker non-interactive.
My code structure:
import SwiftUI
import MapKit
struct UserAnnotationSample: View {
@State private var position: MapCameraPosition = .userLocation(fallback: .automatic)
@State private var selectedItem: MapSelection<MKMapItem>?
var body: some View {
Map(position: $position, selection: $selectedItem) {
// UserAnnotation()
}
.mapControls {
MapUserLocationButton()
}
}
}
Key questions:
How can I replace the empty annotation view with a custom avatar when tapping the user location?
Is there a way to make UserAnnotation interactive with selection?
Should I use tag modifier for custom annotations? What's the proper way to associate selections?
Like many applications, mine involves navigation where the user starts a process on one screen and then progresses through several more steps to reach a conclusion. When he confirms that choice, I need to dismiss the entire stack. In my case, he's browsing contacts, selecting one, and then selecting a communication method from those offered by the contact.
This still appears to be a PITA in SwiftUI. NavigationPath is supposed to provide a way to programmatically control a stack of views. Well... I can't find a single example of how to use it for this, except with absurdly shallow (as in a single level) of child views that all take the same datatype.
Nowhere do I see how to use the path as users proceed through your view hierarchy with NavigationLinks. I have not seen any example of how elements get added to the path or how they are related to each added view. Nor can I find an example of popping views off the stack by removing related elements from the path.
I created a class that encloses a NavigationPath:
@Observable
class NavPathController
{
var path: NavigationPath
init()
{
path = NavigationPath()
}
func popOne()
{
path.removeLast()
}
func popAll()
{
path.removeLast(path.count)
}
}
In my root view, I pass a binding to this controller's NavigationPath when creating the NavigationStack:
@State private var viewStack = NavPathController()
var body: some View
{
NavigationStack(path: $viewStack.path)
{
VStack()
{
NavigationLink(destination: UserFindingView(viewPathController: viewStack), label: { Text("Pick a recipient") })
}
}
And likewise each view passes the same view-path controller object to each child view that's invoked with a NavigationLink (instead of using an environment variable, because I find those hokey). But in the end, the path is empty; not surprisingly, clearing it does not pop the views.
So how is one supposed to make this work?
Topic:
UI Frameworks
SubTopic:
SwiftUI
This is a very strange behavior when pushing vc that I have never seen since I started coding. The pushed ViewController is transparent and only navBarTitle is shown. After the push, you can't control anything unless you go back to the home screen.
STEPS TO REPRODUCE
Long press currency change button below.(currencyWrapper)
Call selectCountry and this bug happens.
SourceCode
let currencyWrapper = UIView()
private func configureCurrencyCard(){
//The strange behavior shows up after long pressing this
currencyWrapper.backgroundColor = .white
currencyWrapper.addTarget(self, action: #selector(changeCurrency))
currencyWrapper.setWidth(currencyChangeIcon.follow(by: 16, x: true))
currencyWrapper.setCenterX(w1/2)
currencyWrapper.setHeight(currencyLabel.follow(by: 12, x: false))
currencyWrapper.roundToCircle(true)
view.addSubview(currencyWrapper)
}
private func selectCountry(country: Country){
let vc = CountryViewController(country: country)
vc.hidesBottomBarWhenPushed = true
navigationController?.pushViewController(vc, animated: true)
}
When I present a view controller, whose view is a SwiftUI View, via presentAsModalWindow(_:) the presented window is no longer centered horizontally to the screen, but rather its origin is there. I know this issue occurs for macOS 15.2+, but can't tell if it is from 15.0+. I couldn't find any documentation on why was this changed.
Here's an example code that represents my architecture:
class RootViewController: NSViewController {
private lazy var button: NSButton = NSButton(
title: "Present",
target: self,
action: #selector(presentView))
override func viewDidLoad() {
super.viewDidLoad()
// Add button to tree
}
@objc func presentView() {
presentAsModalWindow(PresentedViewController())
}
}
class PresentedViewController: NSViewController {
override loadView() {
view = NSHostingView(rootView: MyView())
}
}
struct MyView: View {
/* impl */
}
I have received permission from Apple to access SensorKit data for my app. I have granted all necessary permissions, but no data is being retrieved.
The didCompleteFetch method is being called, but I’m unsure where to find event data like Device Usage and Ambient Light. Additionally, the didFetchResult method is never called.
Could anyone please assist me in resolving this issue? Any guidance or troubleshooting steps would be greatly appreciated.
import SensorKit
class ViewController: UIViewController, SRSensorReaderDelegate {
let store = SRSensorReader(sensor: .deviceUsageReport)
override func viewDidLoad() {
super.viewDidLoad()
requestSensorAuthorization()
}
func requestSensorAuthorization() {
var sensors: Set<SRSensor> = [
.accelerometer,
.deviceUsageReport,
.messagesUsageReport,
.visits,
.keyboardMetrics,
.phoneUsageReport,
.ambientLightSensor
]
if #available(iOS 16.4, *) {
sensors.insert(.mediaEvents)
}
SRSensorReader.requestAuthorization(sensors: sensors) { error in
if let error = error {
print("Authorization failed: \(error.localizedDescription)")
} else {
self.store.startRecording()
self.requestSensorData()
print("Authorization granted for requested sensors.")
}
}
}
func requestSensorData() {
let fromTime = SRAbsoluteTime.fromCFAbsoluteTime(_cf: Date().addingTimeInterval(-60 * 60).timeIntervalSinceReferenceDate)
let toTime = SRAbsoluteTime.fromCFAbsoluteTime(_cf: Date().timeIntervalSinceReferenceDate)
let request = SRFetchRequest()
request.from = fromTime
request.to = toTime
request.device = SRDevice.current
store.fetch(request)
store.delegate = self
}
func sensorReader(_ reader: SRSensorReader, didCompleteFetch fetchRequest: SRFetchRequest) {
print("Fetch request completed: \(fetchRequest.from) to \(fetchRequest.to)")
Task {
do {
let samples = try await reader.fetch(fetchRequest)
print("Samples count: \(samples)")
} catch {
print("Error Fetching Data: \(error.localizedDescription)")
}
}
}
func sensorReader(_ reader: SRSensorReader, fetching fetchRequest: SRFetchRequest, didFetchResult result: SRFetchResult<AnyObject>) -> Bool {
print(result)
return true
}
}
I'm trying to implement the same UI used by the Settings app on iPad: a split view with two columns that are visible at all times.
This code produces the layout i want, but I would like to hide the "toggle sidebar visibility" button that the system introduces.
Is there a SwiftUI API I can use to hide this button? Maybe an alternate way to setup views that tells the system that the button is not necessary?
struct SomeView: View {
var body: some View {
NavigationSplitView(
columnVisibility: .constant(.all),
sidebar: { Text("sidebar") },
detail: { Text("detail") }
)
.navigationSplitViewStyle(.balanced)
}
}
In my project, I am getting some text from backend which could have html tags. For this, I am converting the string to attributed string. However I noticed that when the string has html tags with color in it and when the text is displayed in toolbar, then the text displays with an ellipsis towards the end. Sharing code below:
import SwiftUI
struct ContentViewA: View {
@State private var displayText: AttributedString?
var body: some View {
NavigationStack {
VStack {
Text(displayText ?? "")
}
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button {
} label: {
Text("Done").font(.body.bold())
}
}
ToolbarItem(placement: .principal) {
Text(displayText ?? "")
}
}
.onAppear {
let string = "<div><p><span style=\"color:#FF0000;\">Hello World</span></p></div>"
displayText = string.convertToAttributedString
/// Note: If I don't set the font, then the ellipsis are not displayed in the toolbar, but I need this font style.
displayText?.font = .body.bold()
}
}
}
}
extension String {
var convertToAttributedString: AttributedString? {
guard let data = data(using: .utf8) else { return nil }
var attributedString: AttributedString?
if let nsAttributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue],
documentAttributes: nil) {
attributedString = try? AttributedString(nsAttributedString, including: \.uiKit)
}
return attributedString
}
}
I am printing displayText in the body of the view and am not seeing ellipsis at the end of the string, but in toolbar, I am seeing ellipsis. I am unable to figure out what's causing this and what can be the fix for it. However, if I avoid setting the font on attributed string, then the ellipsis are not displayed in toolbar. However, I need to set the string to a specific font style.
How can I avoid ellipsis in toolbar and while also setting the required font on the string?
Topic:
UI Frameworks
SubTopic:
SwiftUI
Hello, I have a Task model in my application which has an optional many to many relationship to a User model.
task.assignedUsers
user.tasks
I am looking to construct a SwiftData predicate to fetch tasks which either have no assigned users or assigned users does not contain specific user.
Here is a partially working predicate I have now:
static func assignedToOthersPredicate() -> Predicate<Task> {
let currentUserGUID = User.currentUserGUID
return #Predicate<Task> { task in
task.assignedUsers.flatMap { users in
users.contains(where: { $0.guid != currentUserGUID })
} == true
}
}
This only returns tasks assigned to others, but not those which have no assigned users.
If combine it with this:
static func notAssignedPredicate() -> Predicate<Task> {
return #Predicate<Task> { task in
task.assignedUsers == nil
}
}
Then I get a run time crash: "to-many key not allowed here"
What is the proper way to do this?
Thanks.
Hi,
I’m practicing with NavigationSplitView for macOS and customizing the sidebar. I’ve managed to adjust most parts, but I couldn’t remove the sidebar’s divider. It seems like it’s not possible in modern SwiftUI. My AppKit knowledge is also not very strong.
How can I remove the sidebar divider?
I want to use a plain background. I also solved it by creating my own sidebar, but I wanted to try it using NavigationSplitView.
Here is a simple main.swift file of a macOS app:
import SwiftUI
struct ContentView: View {
@State private var selectedItem = 0
@FocusState private var isListFocused: Bool
var body: some View {
List(0..<40, id: \.self, selection: $selectedItem) { index in
Text("\(index)")
.padding()
.focusable()
}
.focused($isListFocused)
.onAppear {
isListFocused = true
}
}
}
func createAppWindow() {
let window = NSWindow(
contentRect: .zero,
styleMask: [.titled],
backing: .buffered,
defer: false
)
window.contentViewController = NSHostingController(rootView: ContentView())
window.setContentSize(NSSize(width: 759, height: 300))
window.center()
window.makeKeyAndOrderFront(nil)
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
createAppWindow()
}
}
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
NSApplication.shared.run()
Try to move the focus with a keyboard slowly as shown on the GIF attached and you'll see that the focus items don't sit in a List's view.
I just made a simple AppKit app, but don't know how to remove borders of rows when they're swiped.
SwiftUI's list does not have this problem though.
Attaching gif demo and code:
import SwiftUI
struct NSTableViewWrapper: NSViewRepresentable {
@State var data: [String]
class Coordinator: NSObject, NSTableViewDataSource, NSTableViewDelegate {
var parent: NSTableViewWrapper
weak var tableView: NSTableView?
init(parent: NSTableViewWrapper) {
self.parent = parent
}
func numberOfRows(in tableView: NSTableView) -> Int {
self.tableView = tableView
return parent.data.count
}
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier("Cell"), owner: nil) as? NSTextField
?? NSTextField(labelWithString: "")
cell.identifier = NSUserInterfaceItemIdentifier("Cell")
cell.stringValue = parent.data[row]
cell.isBordered = false
return cell
}
func tableView(_ tableView: NSTableView, rowActionsForRow row: Int, edge: NSTableView.RowActionEdge) -> [NSTableViewRowAction] {
guard edge == .trailing else { return [] }
let deleteAction = NSTableViewRowAction(style: .destructive, title: "Delete") { action, index in
self.deleteRow(at: index, in: tableView)
}
return [deleteAction]
}
private func deleteRow(at index: Int, in tableView: NSTableView) {
guard index < parent.data.count else { return }
NSAnimationContext.runAnimationGroup({ context in
context.duration = 0.3
tableView.removeRows(at: IndexSet(integer: index), withAnimation: .slideUp)
}, completionHandler: {
DispatchQueue.main.async {
self.parent.data.remove(at: index)
tableView.reloadData()
}
})
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func makeNSView(context: Context) -> NSScrollView {
let scrollView = NSScrollView()
let tableView = NSTableView()
let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("Column"))
column.width = 200
tableView.addTableColumn(column)
tableView.delegate = context.coordinator
tableView.dataSource = context.coordinator
tableView.backgroundColor = .clear
tableView.headerView = nil
tableView.rowHeight = 50
tableView.style = .inset
scrollView.documentView = tableView
scrollView.hasVerticalScroller = true
scrollView.additionalSafeAreaInsets = .init(top: 0, left: 0, bottom: 6, right: 0)
return scrollView
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
(nsView.documentView as? NSTableView)?.reloadData()
}
}
struct ContentView: View {
@State private var itemsString = Array(0..<40).map(\.description)
var body: some View {
NSTableViewWrapper(data: itemsString)
}
}
func createAppWindow() {
let window = NSWindow(
contentRect: .zero,
styleMask: [.titled],
backing: .buffered,
defer: false
)
window.title = "NSTableView from AppKit"
window.contentViewController = NSHostingController(rootView: ContentView())
window.setContentSize(NSSize(width: 759, height: 300))
window.center()
window.makeKeyAndOrderFront(nil)
}
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
createAppWindow()
}
}
let delegate = AppDelegate()
NSApplication.shared.delegate = delegate
NSApplication.shared.run()
In our application we have two usecases for a Hotkey/Shortcut identification API/method.
We have some predefined shortcuts that will ship with our MacOS application. They may or may not change dynamically, based on what the user has already set as shortcuts/hotkeys, and also to avoid any important system wide shortcuts that the user may or may not have changed.
We allow the user to customize the shortcuts/hotkeys in our application, so we want to show what shortcuts the user already has in use system-wide and across their OS experience.
This gives rise to the need for an API that lets us know which shortcut/hotkeys are currently being used by the user and also the current system wide OS shortcuts in use.
Please let me know if there are any APIs in AppKit or SwiftUI we can use for the above
I have the MainView as the active view if the user is logged in(authenticated). the memory allocations when we run profile is pretty good. We have graphql fetching, we have token handling eg: This is All heap:
1 All Heap & Anonymous VM 13,90 MiB 65408 308557 99,10 MiB 373965 Ratio: %0.14, %0.86
After what i have checked this is pretty good for initialise and using multiple repositories eg. But when we change tabs:
1 All Heap & Anonymous VM 24,60 MiB 124651 543832 156,17 MiB 668483 Ratio: %0.07, %0.40
And that is not pretty good. So i guess we need to "kill" it or something. How? I have tried some techniques in a forum this was a recommended way:
public struct LazyView<Content: View>: View {
private let build: () -> Content
@State private var isVisible = false
public init(_ build: @escaping () -> Content) {
self.build = build
}
public var body: some View {
build()
Group {
if isVisible {
build()
} else {
Color.clear
}
}
.onAppear { isVisible = true }
.onDisappear { isVisible = false }
}
}
But this did not help at all. So under here is the one i use now. So pleace guide me for making this work.
import DIKit
import CoreKit
import PresentationKit
import DomainKit
public struct MainView: View {
@Injected((any MainViewModelProtocol).self) private var viewModel
private var selectedTabBinding: Binding<MainTab> {
Binding(
get: { viewModel.selectedTab },
set: { viewModel.selectTab($0) }
)
}
public init() {
// No additional setup needed
}
public var body: some View {
NavigationStack(path: Binding(
get: { viewModel.navigationPath },
set: { _ in }
)) {
TabView(selection: selectedTabBinding) {
LazyView {
FeedTabView()
}
.tabItem {
Label("Feed", systemImage: "house")
}
.tag(MainTab.feed)
LazyView {
ChatTabView()
}
.tabItem {
Label("Chat", systemImage: "message")
}
.tag(MainTab.chat)
LazyView {
JobsTabView()
}
.tabItem {
Label("Jobs", systemImage: "briefcase")
}
.tag(MainTab.jobs)
LazyView {
ProfileTabView()
}
.tabItem {
Label("Profile", systemImage: "person")
}
.tag(MainTab.profile)
}
.accentColor(.primary)
.navigationDestination(for: MainNavigationDestination.self) { destination in
switch destination {
case .profile(let userId):
Text("Profile for \(userId)")
case .settings:
Text("Settings")
case .jobDetails(let id):
Text("Job details for \(id)")
case .chatThread(let id):
Text("Chat thread \(id)")
}
}
}
}
}
import SwiftUI
public struct LazyView<Content: View>: View {
private let build: () -> Content
public init(_ build: @escaping () -> Content) {
self.build = build
}
public var body: some View {
build()
}
}
Hi,
Is there any way of changing the contentInset (UIKit variant) of a List in SwiftUI?
I do not see any APIs for doing so, the closest I gotten is to use safeAreaInset . While visually that works the UX is broken as you can no longer "scroll" from the gap made by the .safeAreaInset(edge:alignment:spacing:content:)
I have subbmited a feedback suggestion: FB16866956
A NavigationStack with a singular enum for .navigationDestination() works fine.
Both NavigationLinks(value:) and directly manipulating the NavigationPath work fine for moving around views. Zero problems.
The issue is when we instead use a NavigationSplitView, I've only dabbled with two-column splits (sidebar and detail) so far.
Now, if the sidebar has its own NavigationStack, everything works nicely on an iPhone, but on an iPad, you can't push views onto the detail from the sidebar. (They're pushed on the sidebar)
You can solve this by keeping a NavigationStack ONLY on the detail. Sidebar links now properly push onto the detail, and the detail can move around views by itself.
However, if you mix NavigationLink(value:) with manually changing NavigationPath, it stops working with no error. If you only use links, you're good, if you only change the NavigationPath you're good. Mixing doesn't work. No error in the console either, the breakpoints hit .navigationDestination and the view is returned, but never piled up. (Further attempts do show the NavigationPath is being changed properly, but views aren't changing)
This problem didn't happen when just staying on NavigationStack without a NavigationSplitView.
Why mix? There's a few reasons to do so. NavigationLinks put the appropriate disclosure indicator (can't replicate its look 100% without it), while NavigationPaths let you trigger navigation without user input (.onChange, etc)
Any insights here? I'd put some code samples but there's a metric ton of options I've tested here.
Hello,
I’m developing an app where I display a SwiftUI view inside a UIHostingController embedded within a UIKit ViewController.
I’m trying to animate the height of the UIHostingController’s view based on a switch’s value, but the SwiftUI view doesn’t animate at all.
Below is a simplified version of my code:
class ViewController: UIViewController {
private lazy var parentView: UIView = {
let view = UIView()
view.backgroundColor = .red
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var hostingView: UIView = {
let testView = TestView()
let hostingController = UIHostingController(rootView: testView)
let view = hostingController.view!
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private lazy var button: UISwitch = {
let button = UISwitch()
button.addTarget(self, action: #selector(onClickSwitch(sender:)), for: .valueChanged)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
private var hostingViewHeightConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(parentView)
parentView.addSubview(hostingView)
parentView.addSubview(button)
NSLayoutConstraint.activate([
parentView.topAnchor.constraint(equalTo: view.topAnchor),
parentView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
parentView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
parentView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
NSLayoutConstraint.activate([
hostingView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor),
hostingView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor),
hostingView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor)
])
hostingViewHeightConstraint = hostingView.heightAnchor.constraint(equalTo: parentView.heightAnchor, multiplier: 0.5)
hostingViewHeightConstraint?.isActive = true
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: parentView.centerXAnchor),
NSLayoutConstraint(item: button,
attribute: .centerY,
relatedBy: .equal,
toItem: parentView,
attribute: .centerY,
multiplier: 0.25,
constant: 0)
])
}
@objc func onClickSwitch(sender: UISwitch) {
hostingViewHeightConstraint?.isActive = false
let multiplier: CGFloat = sender.isOn ? 0.25 : 0.5
hostingViewHeightConstraint = hostingView.heightAnchor.constraint(equalTo: parentView.heightAnchor, multiplier: multiplier)
hostingViewHeightConstraint?.isActive = true
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
I’m looking for the behavior demonstrated in the video below:
Does anyone have suggestions on how to achieve this?
Hello, I've managed to get rid of these spaces in different ways. Using scrollview, giving negative insets, rewriting modifiers from scratch with plain style etc. But I couldn't solve this with a simple solution. I've read comments from many people experiencing similar problems online. It seems like there isn't a simple modifier to remove these spaces when we use sidebar as the list style in SwiftUI, or I couldn't find the simple solution.
I wonder what's the simplest and correct way to reset these spaces?
let numbers = Array(1...5)
@State private var selected: Int?
var body: some View {
List(numbers, id: \.self, selection: $selected) { number in
HStack {
Text("Test")
Spacer()
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.listStyle(.sidebar)
}
}