Posts

Post not yet marked as solved
2 Replies
I am working on a cross platform app and using the app lifecycle across all platforms would be great. Since I can’t do that, my current plan is to keep using the new lifecycle on macOS, but fall back to iOS 13 way on iOS until there is a way to accept CloudKit shares in the new lifecycle.
Post not yet marked as solved
1 Replies
There is only one CloudKit container per app. (Well, I guess there can be several, but mostly you work with one.) That container, though, contains both private, public, and shared databases. It is possible to mix and match the different database types. I am currently working on an app that uses all three database types. So yes, it’s definitely possible.
Post marked as solved
5 Replies
Interesting. I tried to run exactly this, with the viewModel being an ObservedObject that gets passed in. When tapping on the list items, the UI works but appears glitchy, there are some weird flashing artifacts. Updating the selection with timer or external button press does not work.
Post marked as solved
5 Replies
Here is the non-working version with binding. Main app: @main struct ListSelectionApp: App { 		@StateObject var viewModel = ViewModel() 		var body: some Scene { 				WindowGroup { 						ContentView(selection: $viewModel.listSelection, objects: [ListObject(text: "first"), ListObject(text:"second"), ListObject(text: "third")]) 				} 		} } ViewModel: import Foundation struct ListObject: Identifiable, Hashable { 		let id = UUID() 		let text: String } class ViewModel: ObservableObject { 		@Published var listSelection: ListObject? 		{ 				didSet { 						print("did set list selection to \(String(describing: listSelection))") 				} 		} } ContentView: import SwiftUI struct ContentView: View { 		@Binding var selection: ListObject? 		var objects: [ListObject] 		var body: some View { 				NavigationView { 						List { 								ForEach(objects) { object in 										NavigationLink( 												destination: 														ListObjectView(object: object), tag: object, selection: $selection, 												label: { 														Text(object.text) 												}) 								} 						}.listStyle(SidebarListStyle()) 						.toolbar { 								ToolbarItem { 										Button("Push item") { 												selection = objects.first 										} 								} 						} 						Text("Empty view, pick something.").padding() 				}.onAppear { 						DispatchQueue.main.asyncAfter(deadline: .now() + 2) { 								selection = objects.first 						} 				} 		} } When the timer fires or when tapping the button, didSet of the ViewModel property is called as expected, but the UI doesn’t update.
Post marked as solved
8 Replies
Update after WWDC 2020. We got LazyVStack there, which is exactly what’s needed. I discussed this with Apple engineers in a lab, who confirmed that LazyVStack + scrollview + UI/NSTextView is quite acceptable for the time being.
Post not yet marked as solved
2 Replies
What makes you say that it is not supported? According to API documentation - https://developer.apple.com/documentation/swiftui/scenestorage, and the SwiftUI header, it also exists on Mac. @available(iOS 14.0, OSX 10.16, tvOS 14.0, watchOS 7.0, *) @propertyWrapper public struct SceneStorage<Value> : DynamicProperty {
Post marked as solved
8 Replies
Update, July 2020, after WWDC 2020. We got new LazyVStack API, which means we can implement this now with scrollview instead of list. So here is a solution based on ScrollView and attributed string height measuring. I discussed this with Apple engineers during a SwiftUI who said that this is if not the best/official practice, then at least a pretty good solution. It works well for me in both macOS and iOS. (I got an error from the forum system that I have too many characters, therefore including only the iOS native code. macOS NSTextView stuff is very similar in spirit.) // //&#9;Created by Jaanus Kase on 11.05.2020. //&#9;Copyright © 2020 Jaanus Kase. All rights reserved. // import SwiftUI struct NativeTextsWithManagedHeight: View { &#9;&#9;let rows = texts(count: 1000) &#9;&#9;var body: some View { &#9;&#9;&#9;&#9;GeometryReader { geometry in &#9;&#9;&#9;&#9;&#9;&#9;ScrollViewReader { scrollViewProxy in &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;VStack { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ScrollView { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;LazyVStack(spacing: 0) { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;ForEach(0..<self.rows.count, id: \.self) { i in &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;self.makeView(geometry, text: self.rows[i]) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;Button("Scroll to row 3") { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;print("Something") &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;withAnimation { &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;scrollViewProxy.scrollTo("Hello https://example.com: 1 2 3", anchor: .center) &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;}.padding() &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;&#9;&#9;} &#9;&#9;&#9;&#9;} &#9;&#9;} &#9;&#9;func makeView(_ geometry: GeometryProxy, text: String) -> some View { &#9;&#9;&#9;&#9;let attributed = attributedString(for: text) &#9;&#9;&#9;&#9;let height = attributed.height(containerWidth: geometry.size.width) &#9;&#9;&#9;&#9;return NativeTextView(string: text).frame(width: geometry.size.width, height: height).id(text) &#9;&#9;} } struct NativeTextsWithManagedHeight_Previews: PreviewProvider { &#9;&#9;static var previews: some View { &#9;&#9;&#9;&#9;NativeTextsWithManagedHeight(lazyStack: true) &#9;&#9;} } #if os(iOS) typealias NativeFont = UIFont typealias NativeColor = UIColor struct NativeTextView: UIViewRepresentable { &#9;&#9;var string: String &#9;&#9;func makeUIView(context: Context) -> UITextView { &#9;&#9;&#9;&#9;let textView = UITextView() &#9;&#9;&#9;&#9;textView.isEditable = false &#9;&#9;&#9;&#9;textView.isScrollEnabled = false &#9;&#9;&#9;&#9;textView.dataDetectorTypes = .link &#9;&#9;&#9;&#9;textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) &#9;&#9;&#9;&#9;textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) &#9;&#9;&#9;&#9;textView.textContainer.lineFragmentPadding = 0 &#9;&#9;&#9;&#9;let attributed = attributedString(for: string) &#9;&#9;&#9;&#9;textView.attributedText = attributed &#9;&#9;&#9;&#9;return textView &#9;&#9;} &#9;&#9;func updateUIView(_ textView: UITextView, context: Context) { &#9;&#9;} } #endif func attributedString(for string: String) -> NSAttributedString { &#9;&#9;let attributedString = NSMutableAttributedString(string: string) &#9;&#9;let paragraphStyle = NSMutableParagraphStyle() &#9;&#9;paragraphStyle.lineSpacing = 4 &#9;&#9;let range = NSMakeRange(0, (string as NSString).length) &#9;&#9;attributedString.addAttribute(.font, value: NativeFont.systemFont(ofSize: 24, weight: .regular), range: range) &#9;&#9;attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) &#9;&#9;return attributedString } extension NSAttributedString { &#9;&#9;func height(containerWidth: CGFloat) -> CGFloat { &#9;&#9;&#9;&#9;let rect = self.boundingRect(with: CGSize.init(width: containerWidth, height: CGFloat.greatestFiniteMagnitude), &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9; options: [.usesLineFragmentOrigin, .usesFontLeading], &#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9;&#9; context: nil) &#9;&#9;&#9;&#9;return ceil(rect.size.height) &#9;&#9;} } func texts(count: Int) -> [String] { &#9;&#9;return (1...count).map { &#9;&#9;&#9;&#9;(1...$0).reduce("Hello https://example.com:", { $0 + " " + String($1) }) &#9;&#9;} }
Post marked as solved
12 Replies
Update for iOS 14 and macOS Big Sur (i.e this year’s update of SwiftUI). If using a ScrollView instead of list, there is new API for setting the scroll position: https://developer.apple.com/documentation/swiftui/scrollviewproxy/scrollto(_:anchor:) I discussed this with Apple engineers during a SwiftUI lab. No clean API to get scroll position to implement something like infinite load. But at least we can set it cleanly now. For many cases of displaying content, using ScrollView + LazyVStack is better than using a List, and then we can also use this new API.
Post marked as solved
8 Replies
I agree with you about two things.SwiftUI is still obviously very raw and has many rough edges. It will not be equal to AppKit or UIKit any time soon, and this is why bridging the frameworks with these ViewRepresentable things is quite elegant.Sometimes, hacks are needed for workarounds.I am still hoping for a cleaner solution to my problem, and a clearer and deeper explanation from someone who knows more about the internals, or better docs from Apple. This shouldn’t be an edge case, but rather common. And for my own education, I really want to understand why and how it works internally and how do the different pieces interact with one another. There are multiple complex pieces interacting with each other here. UI/NSTextView is quite complex, and its layout behavior seems to differ significantly based on if it is scrollable or not. I don’t entirely understand how SwiftUI sizes its list row heights in this case. UI/NSTextView layout inside the ViewRepresentable can be done with autolayout or resizing mask, all of which have different behavior and performance characteristics. Etc. I do hope to eventually get to the bottom of this and arrive at a clean, elegant, performant solution.I’m keeping my fingers crossed that maybe they bring some updates or better docs/examples at WWDC which is not too far away. And maybe in the meantime, someone can offer a cleaner solution.In the meantime, I came up with this. It meets my requirements in the sense that it sizes the list row heights correctly in most cases. It is ugly because when e.g adding rows to a list, heights get messed up because of the async call, and the performance seems to be not great, causing more redraws/layouts than is necessary. Scrolling is not great. Nevertheless, I leave it here as an interim solution.import SwiftUI let number = 200 struct ListWithNativeTexts: View { var body: some View { List(texts(count: number), id: \.self) { text in NativeTextViewWrapper(string: text) } } } struct NativeTextViewWrapper: View { @State var textViewHeight: CGFloat = 0 var string: String var body: some View { NativeTextView(string: string, textViewHeight: $textViewHeight).frame(height: textViewHeight) } } struct ListWithNativeTexts_Previews: PreviewProvider { static var previews: some View { ListWithNativeTexts() } } func texts(count: Int) -&gt; [String] { return (1...count).map { (1...$0).reduce("Hello https://example.com:", { $0 + " " + String($1) }) } } #if os(iOS) typealias NativeFont = UIFont typealias NativeColor = UIColor typealias NativeView = UIView struct NativeTextView: UIViewRepresentable { var string: String @Binding var textViewHeight: CGFloat func makeUIView(context: Context) -&gt; UITextView { let textView = UITextView() textView.isEditable = false textView.isScrollEnabled = false textView.dataDetectorTypes = .link textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) textView.textContainer.lineFragmentPadding = 0 let attributed = attributedString(for: string) textView.attributedText = attributed measureHeight(view: textView) return textView } func updateUIView(_ textView: UITextView, context: Context) { measureHeight(view: textView) } } #else typealias NativeFont = NSFont typealias NativeColor = NSColor typealias NativeView = NSView struct NativeTextView: NSViewRepresentable { var string: String @Binding var textViewHeight: CGFloat func makeNSView(context: Context) -&gt; NSTextView { let textView = NSTextView() textView.isEditable = false textView.isAutomaticLinkDetectionEnabled = true textView.isAutomaticDataDetectionEnabled = true textView.textContainer?.lineFragmentPadding = 0 textView.backgroundColor = NSColor.clear textView.textStorage?.append(attributedString(for: string)) textView.isEditable = true textView.checkTextInDocument(nil) // make links clickable textView.isEditable = false measureHeight(view: textView) return textView } func updateNSView(_ textView: NSTextView, context: Context) { measureHeight(view: textView) } } #endif extension NativeTextView { func measureHeight(view: NativeView) { DispatchQueue.main.async { let bounds = view.bounds #if os(iOS) let sizeThatFits = view.sizeThatFits(CGSize(width: bounds.size.width, height: CGFloat.greatestFiniteMagnitude)) #else // https://stackoverflow.com/questions/2654580/how-to-resize-nstextview-according-to-its-content var sizeThatFits = CGSize(width: 0, height: 0) if let textView = view as? NSTextView, let layoutManager = textView.layoutManager, let textContainer = textView.textContainer { layoutManager.ensureLayout(for: textContainer) sizeThatFits = layoutManager.usedRect(for: textContainer).size } #endif self.textViewHeight = sizeThatFits.height } } } func attributedString(for string: String) -&gt; NSAttributedString { let attributedString = NSMutableAttributedString(string: string) let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.lineSpacing = 4 let range = NSMakeRange(0, (string as NSString).length) attributedString.addAttribute(.font, value: NativeFont.systemFont(ofSize: 24, weight: .regular), range: range) attributedString.addAttribute(.foregroundColor, value: NativeColor.red, range: range) attributedString.addAttribute(.backgroundColor, value: NativeColor.yellow, range: range) attributedString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) return attributedString }
Post marked as solved
8 Replies
This feels like a hack and against the spirit of the framework. I’m hoping there is a cleaner solution that I have missed, that doesn’t involve leaking anything out of the ViewRepresentable. I also can’t quite figure out what would be a good moment to capture the frame/height info in my solution - should I subclass UITextView/NSTextView and intercept the frame/bounds in some of the view layout methods? Uhh.
Post marked as solved
8 Replies
This doesn’t make a difference because .byWordWrapping is the default value for lineBreakMode. The text views wrap lines correctly if given enough space. If I set an explicit height on the row, I see everything is wrapped correctly on word breaks:NativeTextView(string: text).frame(height: 128)
Post marked as solved
12 Replies
Haven’t heard anything about it anywhere, sadly.
Post marked as solved
7 Replies
I’m trying to implement a QuickLook preview of an image with SwiftUI. Thank you for posting this example. Almost got it working. In my case, it is a regular QLPreview of an image, not AR. So I could simplify this code and make it work.Here’s a problem I have: whether I push the preview into navigation stack with NavigationLink or show it with the sheet technique, it only shows the image itself (fully interactive and zoomable, which is great), but it does not show any of the surrounding controls (title, share button) etc. It is just a blank interface with the image. Am I doing anything wrong / is there anything missing in your example to also get the title and Share button for a regular QLPreview?
Post marked as solved
8 Replies
Fair enough. Thank you. Looks like I should migrate all the data to a new container owned by B, and then release a new version of the app talking to that container. The volume of the data is fairly small, and it's doable by hand.I was really expecting the CloudKit container to be migrated over along with the rest of the app transfer. I can sort of understand why it wasn't (there isn’t really 1:1 mapping or association between apps and containers). I don’t believe I’m the first in the world to encounter this scenario - surely, apps using CloudKit must have been sold and transferred. Really curious what they have done in this case - especially if they use private containers, which the developer cannot migrate just like that. (My case only involves public container and all data is available to me.)
Post marked as solved
8 Replies
"Is it correct that Developer B does not have access to the container but Developer A still does?" correct. Developer A can access the container in CloudKit dashboard. Developer B can not.For the second one - sorry, what I wrote is ambiguous. I mean: "The app signed by developer account B can still access the CK container owned by developer account A (ownership is manifested by the fact that A can access it in CK dashboard, and B can not)."