SwiftUI Preview Sheet w/o Running Live Preview? - swiftui

Update Xcode 13
The code sample below works as expected in Xcode 13.
Update from Apple Frameworks Engineer October 2020:
Unfortunately there is no current workarounds to let you preview this outside of the live preview.
Is it possible to create a SwiftUI preview of a presented sheet without running the Live Preview? For example:
struct Sheet_Previews: PreviewProvider {
static var previews: some View {
Text("Background").sheet(isPresented: .constant(true)) {
Text("Sheet")
}
}
}
The above results in the following preview:
In order for the sheet content to be presented in the preview, you must run the Live Preview:

Xcode 13.0 seems to handle this correctly without starting a Live Preview.
So this is working now:
struct Sheet_Previews: PreviewProvider {
static var previews: some View {
Text("Background").sheet(isPresented: .constant(true)) {
Text("Sheet")
}
}
}

Just let preview provide to show both views, for parent and sheet, like
struct Sheet_Previews: PreviewProvider {
static var previews: some View {
Group { // << will create two previews
Text("Background") // << parent view here
Text("Sheet") // << sheet view here
}
}
}

Related

Why doesn't clearing the new iOS 16 SwiftUI NavigationPath to "pop to root" animate smoothly back to the root view?

I have a new iOS 16 SwiftUI NavigationStack with navigation determined by the NavigationDestination modifier which works fine.
My question is why doesn't it animate smoothly by sliding back to the root view when clearing the NavigationPath if you are more than one view deep within the stack?
It works if you are only one level deep, but anything lower than that causes "popping to root" to just jump back to the root view without the sliding animation.
Is this a "feature" or bug or am I doing something incorrectly?
Steps to re-create the issue
Run the sample code below.
Click the first navigation link and then click "Pop To Root View" - notice that it "slides smoothly" back to root view.
Click the first or second link - then click the "Navigate to View 3" which shows view 3.
Then click "Pop to Root" and you'll notice that it jumps back to the root view rather than slides. That's my question - should it jump back or slide back?
Demo of Issue
Demo Code (using Xcode 14.0 and iOS 16.0):
import SwiftUI
struct DemoPop: View {
#State private var path = NavigationPath()
var body: some View {
VStack {
NavigationStack(path: $path) {
List {
Section("List One") {
NavigationLink("Navigate to View 1", value: "View 1")
NavigationLink("Navigate to View 2", value: "View 2")
}
}
.navigationDestination(for: String.self) { textDesc in
VStack {
Text(textDesc).padding()
Button("Navigate to View 3") {
path.append("View 3")
}.padding()
Button("Pop to Root View") {
path.removeLast(path.count)
}.padding()
}
}
.navigationTitle("Test Pop To Root")
}
}
}
}
struct DemoPop_Previews: PreviewProvider {
static var previews: some View {
DemoPop()
}
}
Update 1:
Think the code above is correct so possibly a bug as mentioned in comments as I have just seen a YouTube video that exhibits the same behaviour - Youtube tutorial - around time line 19:25 - you will see pop to root just jumps back to start.
Update 2:
This has been fixed in iOS 16.2
Most likely a bug, please file a feedback w/ Apple.
That said, if you're on iOS, you can work around it with the following hack:
import UIKit
let animation = CATransition()
animation.isRemovedOnCompletion = true
animation.type = .moveIn
animation.subtype = .fromLeft
animation.duration = 0.3
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
UIApplication.shared.keyWindow!.layer.add(animation, forKey: nil)
self.navigationPath.removeLast()
This has been fixed by Apple on iOS 16.2
For iOS 16.0 and 16.1 here's a 100% working, one-liner solution. Add this SPM library to your codebase https://github.com/davdroman/swiftui-navigation-transitions (version 0.7.1 or later) and simply:
import NavigationTransitions
import SwiftUI
struct DemoPop: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
List {
Section("List One") {
NavigationLink("Navigate to View 1", value: "View 1")
NavigationLink("Navigate to View 2", value: "View 2")
}
}
.navigationDestination(for: String.self) { textDesc in
VStack {
Text(textDesc).padding()
Button("Navigate to View 3") {
path.append("View 3")
}.padding()
Button("Pop to Root View") {
path.removeLast(path.count)
}.padding()
}
}
.navigationTitle("Test Pop To Root")
}
.navigationTransition(.default) // one-liner ✨
}
}
struct DemoPop_Previews: PreviewProvider {
static var previews: some View {
DemoPop()
}
}

How to create multiple previews of cells based on test data in SwiftUI with a foreach

I am trying to display multiple previews of the card view (Apple's SwiftUI tutorial). I managed to do it manually using the Group struct.
struct CardView_Previews: PreviewProvider {
static var previews: some View {
Group {
CardView(scrum: DailyScrum.data[0])
.background(DailyScrum.data[0].color)
CardView(scrum: DailyScrum.data[1])
.background(DailyScrum.data[1].color)
}.previewLayout(.fixed(width: 400, height: 60))
}
}
I would like to automate the display of four cells based on test data with a ForEach, but the following fails to build and the compiler fails to produce a diagnostic for the expression.
struct CardView_Previews: PreviewProvider {
static var previews: some View {
Group {
ForEach(DailyScrum.data) { scrum in
CardView(scrum: scrum)
.background(scrum.color)
}
}.previewLayout(.fixed(width: 400, height: 60))
}
}
Is there a way to construct the previews without explicitly creating each preview ?
You can't think of a PreviewProvider as part of your running app. It ONLY is used when you are using a preview on a canvas for that particular view. Therefore, any data you need to provide to the view, must be provided from, or through, the PreviewProvider itself.
From your code, it appears that your model, DailyScrum contains an array data that you use to fill the cells. Therefore, if you want to declare it in the PreviewProvider you would declare it something like this:
struct CardView_Previews: PreviewProvider {
let dailyScrum = DailyScrum(
// Initialize your data here
)
static var previews: some View {
Group {
ForEach(dailyScrum.data) { scrum in
CardView(scrum: scrum)
.background(scrum.color)
}
}.previewLayout(.fixed(width: 400, height: 60))
}
}
If this is your model data that you may use in other views, then you may want to follow DRY and not repeat yourself by declaring the same data in all of your PreviewProviders. In that case I would make a static let of your data IN the model itself like this:
...
static let previewData = DailyScrum(
// Initialize your data here
)
...
and then use it in your PreviewProvider like this:
struct CardView_Previews: PreviewProvider {
static var previews: some View {
Group {
ForEach(DailyScrum.previewData) { scrum in
CardView(scrum: scrum)
.background(scrum.color)
}
}.previewLayout(.fixed(width: 400, height: 60))
}
}
The way this is written assumes that DailScrum conforms to Identifiable. If it doesn't, it should, otherwise you will have to write your ForEach as ForEach(DailyScrum.previewData, id: \.self) or some other part of the model that you can use to identify the individual elements.
I found the workaround I was looking for by simply adding the id argument to the ForEach initializer. It prevents from having to add Identifiable conformance to the type :
struct CardView_Previews: PreviewProvider {
static var previews: some View {
Group {
ForEach(DailyScrum.data, id: \.title) { scrum in
CardView(scrum: scrum)
.background(scrum.color)
}
}.previewLayout(.fixed(width: 400, height: 60))
}
}

SwiftUI: ContextMenu flipping bug?

I am implementing a chat in SwiftUI. I am using a double rotated list to show the most recent chat messages on the bottom. Each message has a context menu attached. But there is bug: when tapping on the context menu, the message does an erroneous rotation animation. It looks bad. Using XCode 12.2.
Any ideas how to get this working? I did file a bug report with Apple, but that typically takes months (years?).
Here is basic example:
import SwiftUI
struct FlippedContextMenuBug: View {
var body: some View {
ScrollView {
ForEach(0..<100) { i in
Text("Message \(i)")
.padding()
.contextMenu(menuItems: {
Text("Menu Item 1")
})
.rotationEffect(.pi)
}
}
.rotationEffect(.pi)
}
}
struct FlippedContextMenuBug_Previews: PreviewProvider {
static var previews: some View {
FlippedContextMenuBug()
}
}
Thank you!

SwiftUI - Picker not working when inside a Form (iPad split view)

I'm using Xcode 11 GM
The hierarchy I have is:
List > Form (Picker)
This is the code for List:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView{
List {
NavigationLink(destination: FormView())
{
Text("Item 1")
}
}
.navigationBarTitle("List")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And this is the code for the Form:
import SwiftUI
struct FormView: View {
var body: some View {
Form {
Picker(selection: .constant(1), label: Text("Picker")) {
Text("1").tag(1)
Text("2").tag(2)
}
}
.navigationBarTitle("Form")
}
}
struct FormView_Previews: PreviewProvider {
static var previews: some View {
FormView()
}
}
The problem is:
When I build on iPad split view, tap to select works as expected:
But, when inside tags I cannot select them, nor it will go back to form view:
On iPhone, it works fine...
Is this a known bug?
Cheers to all
In my experience, it seems that the picker cells expect the user to tap on the Text elements themselves.
I don't know why. I think it's weird, too, and probably a bug. Not sure Apple knows about it yet. I sure haven't filed a radar.
To confirm this, try testing with longer text in the picker items.
What I normally do is implement a simple picker myself (not that hard, just a List and some items) and implement the items as Buttons. (If it changes the color on me, I change it back using .foregroundColor(.primary)). What's nice about this is that a Button inside a Form or a List renders as the table view cell we're used to, with the same highlighting interaction we all know and love!

Xcode 11 -- SwiftUI's dark mode setup

Okay. I know this shouldn't be rocket science. I can't seem to get dark mode working and I've read the documentation a few times. Hoping someone can pick out what I'm missing.
I have an named color in the asset catalog.
I set my plist mode to be in dark mode for easier testing.
My content view looks like this:
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World")
Text("Yo yo yo")
.color(Color("darkModeColor"))
}
}
}
No matter what I do, the color is ALWAYS of the "Any" appearance when it should be taking on the cyan color.
I know dark mode itself works because all the system semantic colors provide by apple are working just fine as you can see the "Hello World" text changed to white.
Any ideas what I'm missing or is anyone else running into this issue with Xcode 11 Beta 1?
This is probably a bug in this beta release of Xcode 11. I tried the dark mode feature in a UIKit application and it is working perfectly using UIColor but the same color wouldn’t work using Color in SwiftUI. Hopefully this will be fixed in the next releases.
Update: This issue was fixed with Xcode 11 beta 3.
A working (but quite verbose) solution we can use to overcome this current limitation is to extend Color with methods parameterised with the current color scheme as follows:
import SwiftUI
extension Color {
static let lightBackgroundColor = Color(white: 1.0)
static let darkBackgroundColor = Color(white: 0.0)
static func backgroundColor(for colorScheme: ColorScheme) -> Color {
if colorScheme == .dark {
return darkBackgroundColor
} else {
return lightBackgroundColor
}
}
}
And in the views where you need to access these colors you would add an environment property for the color scheme and use it to retrieve the dynamic color:
import SwiftUI
struct ColoredView : View {
#Environment(\.colorScheme) var colorScheme: ColorScheme
var body: some View {
Rectangle().fill(Color.backgroundColor(for: self.colorScheme))
}
}
These colours defined in code work for Xcode previews as well as the simulator.
You can change color scheme in your preview
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView().environment(\.colorScheme, .dark)
}
}
upd: and you can create any previews for light and dark modes
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
Group {
ContentView()
ContentView().environment(\.colorScheme, .dark)
}
}
}