I was wondering whether objects in Core Data (and CloudKit) conform, or are able to be conformed, to the Transferable protocol. As far as I can see there is no underlying implementation yet, but maybe this will be added as a future feature so that SwiftUI and Core Data can work better together.
In the WWDC22 session "Enhance collaboration experiences with Messages" at 10:21, a struct call Note was implementing the transferRepresentation required property and returned an object of type CKShareTransferRepresentation from its body. I couldn't find any reference to this type anywhere in the documentation and nothing else was mentioned in the session video. Maybe this is something that is going to be added soon or was removed after the video was made.
Since I want to use Core Data in my SwiftUI app and want to enable sharing features, such as collaboration via the shared database, NSManagedObject subclasses need some way of conforming to the Transferable protocol. I was hoping there would be a specialised way to do this, and the aforementioned type from the session video sort of hinted at this.
I am just asking here to see if anyone has any information about this – whether this feature is about to be released, how this issue can be solved, or another way to do this. Any help is appreciated.
Post
Replies
Boosts
Views
Activity
I am attempting to create a custom font picker (similar to the one in Pages) using SwiftUI, because the current UIFontPickerViewController isn't sufficient enough for my app.
When running the app and presenting FontPickerView in a sheet, the app seems to pause, the sheet doesn't appear, and a lot of dialog continuously pops up in the Xcode console.
This is an example of what keeps showing:
CoreText note: Client requested name ".SFUI-Regular", it will get TimesNewRomanPSMT rather than the intended font. All system UI font access should be through proper APIs such as CTFontCreateUIFontForLanguage() or +[UIFont systemFontOfSize:].
The .SFUI string changes with different font styles, for example -Bold, -Compressed, -SemiExpandedLight...
I believe the problem lies when accessing the UIFont family and font name properties and methods.
Is there a reason why this is happening? A possible solution?
Help is appreciated.
Here is some code you can test (Xcode 13 beta 3):
extension Character {
var isUppercase: Bool {
String(self).uppercased() == String(self)
}
}
extension UIFont {
// Will show dialog from here
class func familyName(forFontName fontName: String) -> String {
var familyName = ""
familyNames.forEach { family in
fontNames(forFamilyName: family).forEach { font in
if font == fontName {
familyName = family
}
}
}
return familyName
}
}
struct FontPickerView: View {
@Environment(\.dismiss) private var dismiss
@ScaledMetric private var linkPaddingLength = 24
@State private var searchText = ""
@State private var linkSelection: String?
@Binding var selectedFontName: String
// Will show dialog from here
private var familyNames: [String] {
UIFont.familyNames.filter {
$0.contains(searchText) || searchText.isEmpty
}
}
// Will show dialog from here
private var familyPickerBinding: Binding<String> {
Binding {
UIFont.familyName(forFontName: selectedFontName)
} set: {
selectedFontName = UIFont.fontNames(forFamilyName: $0)[0]
}
}
var body: some View {
NavigationView {
List {
Picker("All Fonts", selection: familyPickerBinding) {
ForEach(familyNames, id: \.self, content: pickerRow)
}
.labelsHidden()
.pickerStyle(.inline)
}
.listStyle(.insetGrouped)
.searchable(text: $searchText, placement: .navigationBarDrawer)
.navigationTitle("Fonts")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
Button("Cancel", action: dismiss.callAsFunction)
}
}
}
private func linkPadding(forFamilyName familyName: String) -> CGFloat {
familyPickerBinding.wrappedValue == familyName ? 0 : linkPaddingLength
}
private func familyText(forFamilyName familyName: String) -> some View {
Text(familyName)
.font(.custom(familyName, size: UIFont.labelFontSize))
}
private func familyTextWithStyles(forFamilyName familyName: String) -> some View {
ZStack(alignment: .leading) {
NavigationLink("Style options", tag: familyName, selection: $linkSelection) {
FontStylesView(selection: $selectedFontName, family: familyName)
}
.hidden()
HStack {
familyText(forFamilyName: familyName)
Spacer()
Button(action: { linkSelection = familyName }) {
Label("Style options", systemImage: "info.circle")
.labelStyle(.iconOnly)
.imageScale(.large)
}
.buttonStyle(.borderless)
.padding(.trailing, linkPadding(forFamilyName: familyName))
}
}
}
private func pickerRow(forFamilyName familyName: String) -> some View {
Group {
if UIFont.fontNames(forFamilyName: familyName).count == 1 {
familyText(forFamilyName: familyName)
} else {
familyTextWithStyles(forFamilyName: familyName)
}
}
}
}
struct FontStylesView: View {
@Binding var selection: String
let family: String
var body: some View {
List {
Picker("Font Styles", selection: $selection) {
// Will show dialog from here
ForEach(UIFont.fontNames(forFamilyName: family), id: \.self) { font in
Text(fontType(forFontName: font))
.font(.custom(font, size: UIFont.labelFontSize))
}
}
.labelsHidden()
.pickerStyle(.inline)
}
.navigationTitle(family)
}
private func fontType(forFontName fontName: String) -> String {
if let index = fontName.lastIndex(of: "-") {
var text = String(fontName.suffix(from: index).dropFirst())
// Add spaces between words.
let indexes = text.enumerated().filter { $0.element.isUppercase }.map { $0.offset }
var count = 0
indexes.forEach { index in
guard index > 0 else { return }
text.insert(" ", at: text.index(text.startIndex, offsetBy: index + count))
count += 1
}
return text
} else {
return "Regular"
}
}
}
(There are a few bugs that I am going to fix.)
I am trying to replicate the timer picker from the Clock app, shown below, using SwiftUI.
DatePicker doesn't have a countDownTimer mode like UIDatePicker does, but then that doesn't show seconds even though the countDownDuration property is in seconds.
I am currently trying to use a custom UIPickerView with two components (minutes and seconds), all wrapped inside a UIViewRepresentable.
This sort of works, but I can't seem to get the min and sec labels to be positioned where they are.
Any help on this matter would be much appreciated.
Is there any way to set preferred actions for alerts in SwiftUI?
You can set .cancel roles for alert buttons, but they appear in bold and I want the other preferred action to be in bold.
I could set the preferred action to have the .cancel role, but I don't think you're supposed to do it like that.
I haven't found a proper way yet, but I haven't played around with alerts enough to know.
Thanks for any suggestions.
I think that the list row separator insets needs to be fixed in SwiftUI.
For example, when using the .sidebar list style in a compact environment, the separators are inset to a fixed position, and depending on the list row's content, will look normal or out of place.
In one of last year's session videos (wwdc20-10026), Apple even said how to use separators properly, insetting them to align with the primary content of the cell.
If there is a way that I don't know of to control this, I would very much like to know.
Otherwise this needs to be sorted either introducing a new view modifier, or automatically deciding how much to inset by based on the list row content: with Label, inset to align with the title.
I have decided to try out a light theme in Xcode (don’t ask why), when light mode is on, and then use a dark theme when the system switches to dark mode. However, when Xcode goes dark, the editor stays with the light theme and doesn’t switch to an equivalent dark one.
I would like to know if there is a way to have Xcode automatically do this - for example, in light mode use Presentation (Light) and in dark mode use Presentation (Dark).
As of yet, I haven’t found a solution within Xcode, but maybe there is a way to control this by using a custom theme?
I have stumbled upon a problem that I somehow haven't come across previously. It seems like a simple thing that can be done in SwiftUI that I can't seem to solve.
I would like to be able to switch between GroupedListStyle and InsetGroupedListStyle for compact and regular horizontal size classes, respectively.
In iOS 13, since there was no InsetGroupedListStyle, SwiftUI automatically switched between the two on change of horizontal size class, but this feature has been removed. I'd like to have thought a replacement for this had been implemented but supposedly not (that I know of).
I thought that applyingSwift
.listStyle(horizontalSizeClass == .compact ? GroupedListStyle() : InsetGroupedListStyle())would work, but apparently not. Although they both conform to ListStyle, Xcode says they have mismatching types.
I tried then extracting it to a variable:Swift
private var listStyle: some ListStyle {
if horizontalSizeClass == .compact {
return GroupedListStyle()
} else {
return InsetGroupedListStyle()
}
}That didn't work resulting in the same error.
Naively, I added @ViewBuilder to see if that would work until realising these weren't views being returned.
There is no type erased list style (AnyListStyle) so that can't be applied in this situation.
I thought about making a custom ListStyle but the protocol requirements put me off. Even a custom ViewModifier didn't work.
Is this even possible in SwiftUI, or am I missing something obvious?
I have a basic ARSCNView that detects images and displays information about them.
The image detected is currently covered in a slightly transparent black with the text overlaid in a transparent white colour.
Ideally I'd like the background behind the text to be a blur view with that transparent black tint, so it blurs the image that is being detected.
I started trying to implement a UIVisualEffectView with a UIBlurEffect but that didn't work. I also looked up SKEffectNode with a CIFilter but that resulted in nothing changing.
Can you use UIBlurEffect or an alternative blur view to apply to the plane or planeNode?
All solutions appreciated.
Swift
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) - SCNNode? {
guard let imageAnchor = anchor as? ARImageAnchor, let name = imageAnchor.referenceImage.name, let datum = data[name] else { return nil }
let spacing = Float(0.005)
let node = SCNNode()
let plane = SCNPlane(width: imageAnchor.referenceImage.physicalSize.width, height: imageAnchor.referenceImage.physicalSize.height)
plane.firstMaterial?.diffuse.contents = UIColor.black.withAlphaComponent(0.99)
// add blur to this plane with the black tint
let planeNode = SCNNode(geometry: plane)
planeNode.eulerAngles.x = -.pi / 2
node.addChildNode(planeNode)
let titleNode = textNode(datum.title, font: UIFont.boldSystemFont(ofSize: 10))
titleNode.pivot = SCNMatrix4MakeTranslation(titleNode.boundingBox.min.x, titleNode.boundingBox.max.y, .zero)
titleNode.position.x -= Float(plane.width) / 2 - spacing
titleNode.position.y += Float(plane.height) / 2 - spacing
planeNode.addChildNode(titleNode)
let descriptionNode = textNode(datum.description, font: UIFont.systemFont(ofSize: 4), maxWidth: Int(imageAnchor.referenceImage.physicalSize.width * 500))
descriptionNode.pivot = SCNMatrix4MakeTranslation(descriptionNode.boundingBox.min.x, descriptionNode.boundingBox.max.y, .zero)
descriptionNode.position.x -= Float(plane.width) / 2 - spacing
descriptionNode.position.y = titleNode.position.y - titleNode.height - spacing
planeNode.addChildNode(descriptionNode)
return node
}
private func textNode(_ string: String, font: UIFont, maxWidth: Int? = nil) - SCNNode {
let text = SCNText(string: string, extrusionDepth: 0.5)
text.flatness = 0.1
text.font = font
text.firstMaterial?.diffuse.contents = UIColor.lightText
if let maxWidth = maxWidth {
text.containerFrame = CGRect(origin: .zero, size: CGSize(width: maxWidth, height: 100))
text.isWrapped = true
}
let textNode = SCNNode(geometry: text)
textNode.scale = SCNVector3(0.002, 0.002, 0.002)
return textNode
}
I have a UISplitViewController with a separate view controller for compact width. The master view controller shows a table view with a grouped style. Regular size class is insetGrouped and compact is grouped.
The view controllers have two paths, compact and regular width, but the only differences between the two are the table view style. The classes are the same as is the table view content.
I have to present a modal with a table view controller inside that needs to adapt to the size class. Before the modal is presented, I instantiate the right view controller for the current size class, however if the size class changes during presentation, the table view style doesn't change accordingly.
Is there a better way to handle the user changing the size class? Or just a general way to use a UISplitViewController and UITableView.Style with different size classes?
Many thanks for any solutions.
I have a macOS app and i want to align a VStack of HStacks so that the label in the HStack in aligned .trailing and the other view is aligned .leading relative to all the other HStacks.
An example would be in Settings > General on Big Sur where the labels are aligned and the other controls (pickers, toggles) are aligned with each other.
How can I achieve this in SwiftUI?
VStack {
HStack {
Text("Label1") // align trailing with others
Picker("Picker1", selection: $picker1) { ... } // align leading with others
}
Divider()
HStack {
Text("Label2") // align trailing with others
Toggle("Toggle1", isOn: $toggle1) // align leading with others
}
Divider()
HStack {
Text("Label3") // align trailing with others
Picker("Picker2", selection: $picker2) { ... } // align leading with others
}
}
I have a macOS app that allows users to type in numbers which I originally used a custom NSWindow for but now using .keyboardShortcut(_:modifiers:).
I’ve put a CommandMenu in the App file attached to the WindowGroup Scene but then that appears in the menu bar at the top. Every time one of the keys are pressed the menu bar section flashes which can get quite distracting.
Is there a way that you can add a CommandMenu or just the keyboard shortcuts so they are not in the menu bar or at least not visible to the user?
.commands {
CommandMenu("Keyboard") {
Group {
Button("0") { print("0") }
.keyboardShortcut("0", modifiers: .numberPad)
... // 1 to 8
Button("9") { print("9") }
.keyboardShortcut("9", modifiers: .numberPad)
}
Button("Delete") { print("delete") }
.keyboardShortcut(.delete, modifiers: .numberPad)
Button("Enter") { print("enter") }
.keyboardShortcut(.return, modifiers: .numberPad)
}
}
Since the header of a List can't be customised as much as I'd like it to be, I've used a workaround with a ScrollView and a LazyVStack.
ScrollView {
LazyVStack(pinnedViews: .sectionHeaders) {
ForEach(...)
}
}
However, I can't seem to get the separators right or the amount of padding as well the the default separator insets.
I am making the separators with Divider() but even with dynamic type the views becomes 'broken'.
Is there a way to recreate a List with the standard appearance without using one?
Help is much appreciated.
How can I show a default view when there's no selection like in a normal NavigationView but with a UINavigationController?
I've tried this
} content: {
NavigationView {
Text("Hello, World!")
.navigationTitle("Title")
Text("No selection")
}
.navigationBarHidden(true)
}
but then the search bar doesn't show.
struct SearchBarNavigationView<SearchResultsContent: View, Content: View>: UIViewControllerRepresentable {
class Coordinator: NSObject, UISearchResultsUpdating {
private let parent: SearchBarNavigationView
init(_ parent: SearchBarNavigationView) {
self.parent = parent
}
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text else { return }
DispatchQueue.main.async { self.parent.text = searchText.trimmingCharacters(in: .whitespaces) }
}
}
@Binding private var text: String
private let searchResultsContent: SearchResultsContent
private let content: Content
init(text: Binding<String>, @ViewBuilder searchResultsContent: () -> SearchResultsContent, @ViewBuilder content: () -> Content) {
_text = text
self.searchResultsContent = searchResultsContent()
self.content = content()
}
func makeUIViewController(context: Context) -> UINavigationController {
let rootViewController = UIHostingController(rootView: content)
let navigationController = UINavigationController(rootViewController: rootViewController)
navigationController.navigationBar.prefersLargeTitles = true
let searchResultsController = UIHostingController(rootView: searchResultsContent)
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = context.coordinator
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.autocapitalizationType = .none
navigationController.navigationBar.topItem?.searchController = searchController
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
if let searchResultsController = uiViewController.navigationBar.topItem?.searchController?.searchResultsController as? UIHostingController<SearchResultsContent> {
searchResultsController.rootView = searchResultsContent
searchResultsController.view.setNeedsDisplay()
}
if let rootViewController = uiViewController.topViewController as? UIHostingController<Content> {
rootViewController.rootView = content
rootViewController.view.setNeedsDisplay()
}
uiViewController.navigationBar.topItem?.searchController?.searchBar.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
SearchBarNavigationView(text: $searchText) {
Text(searchText)
} content: {
Text("Hello, World!")
.navigationTitle("Title")
Text("No selection") // make this show when no selection
}
.ignoresSafeArea()
I have implemented a custom NavigationView with a search bar in SwiftUI.
The SearchResultsContent doesn’t update properly though. I can’t seem to figure out why.
I’ve done all the UISearchResultsUpdating related things, but that doesn’t fix the problem.
struct SearchBarNavigationView<SearchResultsContent: View, Content: View>: UIViewControllerRepresentable {
class Coordinator: NSObject, UISearchResultsUpdating {
private let parent: SearchBarNavigationView
init(_ parent: SearchBarNavigationView) {
self.parent = parent
}
func updateSearchResults(for searchController: UISearchController) {
guard let searchText = searchController.searchBar.text else { return }
DispatchQueue.main.async { self.parent.text = searchText }
}
}
@Binding private var text: String
private let searchResultsContent: SearchResultsContent
private let content: Content
init(text: Binding<String>, @ViewBuilder searchResultsContent: () -> SearchResultsContent, @ViewBuilder content: () -> Content) {
_text = text
self.searchResultsContent = searchResultsContent()
self.content = content()
}
func makeUIViewController(context: Context) -> UINavigationController {
let rootViewController = UIHostingController(rootView: content)
let navigationController = UINavigationController(rootViewController: rootViewController)
navigationController.navigationBar.prefersLargeTitles = true
let searchResultsController = UIHostingController(rootView: searchResultsContent)
let searchController = UISearchController(searchResultsController: searchResultsController)
searchController.searchResultsUpdater = context.coordinator
searchController.obscuresBackgroundDuringPresentation = false
searchController.searchBar.autocapitalizationType = .none
navigationController.navigationBar.topItem?.searchController = searchController
return navigationController
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
if let searchResultsController = uiViewController.navigationBar.topItem?.searchController?.searchResultsController as? UIHostingController<SearchResultsContent> {
searchResultsController.rootView = searchResultsContent
searchResultsController.view.setNeedsDisplay()
}
if let rootViewController = uiViewController.topViewController as? UIHostingController<Content> {
rootViewController.rootView = content
rootViewController.view.setNeedsDisplay()
}
uiViewController.navigationBar.topItem?.searchController?.searchBar.text = text
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
Implementation in View
SearchBarNavigationView(text: $searchText) {
Text(searchText) // doesn’t change whilst typing
} content: {
List(0 ..< 20) {
Text("Row \($0)")
}
}
.ignoresSafeArea()
.onChange(of: searchText) { value in
print(value) // works correctly
}
I have a LazyVGrid of items which when tapped I want to showing details of that item.
When the item is tapped the item should animate to the detail view with matchedGeometryEffect. However this doesn’t work. Even with the withAnimation there is no animation.
How can this code work with matchedGeometryEffect?
Any help will be appreciated.
I have replaced the items in the grid with numbers to make it simpler.
struct ContentView: View {
@Namespace private var animation
@State private var showingDetail = false
@State private var selection = -1
var detail: some View {
VStack {
Spacer()
Image(systemName: "\(selection).square")
.resizable()
.scaledToFit()
.matchedGeometryEffect(id: selection, in: animation)
Spacer()
Text("Number \(selection)")
.font(.headline)
.padding(.bottom)
}
}
var grid: some View {
ScrollView {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100, maximum: 100))]) {
ForEach(0 ..< 20) { num in
Image(systemName: "\(num).square")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
.matchedGeometryEffect(id: num, in: animation)
.onTapGesture {
selection = num
withAnimation { showingDetail = true }
}
}
}
}
}
var body: some View {
NavigationView {
Group {
if showingDetail {
detail
.toolbar {
Button {
withAnimation { showingDetail = false }
} label: {
Image(systemName: "xmark.circle.fill")
.imageScale(.large)
.foregroundColor(.secondary)
}
}
} else {
grid
}
}
.navigationTitle(showingDetail ? "" : "Numbers")
.navigationBarTitleDisplayMode(showingDetail ? .inline : .automatic)
}
}
}