I have created a grid of show cards, consisting of an image and a name, which is populated by the results of a Realm query. However, when I attempt to delete an object from the Realm database, I expect it to animate its removal, but instead, it simply disappears. I have tried using a normal Realm write transaction but still have not achieved the desired outcome. The ShowResult type conforms to the ObjectKeyIdentifiable protocol.
Can someone please help me understand what I am doing wrong?
struct MainScreen: View {
var columns: [GridItem] = Array(repeating: .init(.flexible(), spacing: 20), count: 2)
#ObservedResults(ShowResult.self) var storedShows
#Environment(\.realm) var realm
var body: some View {
ZStack {
Color("Primary")
.ignoresSafeArea()
ScrollView {
LazyVGrid(columns: columns, spacing: 20) {
ForEach(storedShows) { show in
ShowCard(show: show)
.onTapGesture {
withAnimation {
$storedShows.remove(show)
}
}
}
}
.padding(20)
}
}
}
}
Related
I am looking to use the Timelineview to automatically update the background colour for a popup sheet that is displaying a list.
I am trying to figure out how to only call it once so I can use it for multiple sections in one list and in different views that display the type of data but with different information.
I have custom colours that I would like to use and call them from the assets catalogue.
A Sample Code:
List {
Section(header: Text("Ear")
.modifier(SectionHeaderModifer())) {
ForEach(Ear.indices, id: \.self) {index in
VStack {
VStack() {
Spacer()
Text(Ear[index].name)
.modifier(NameModifier())
}
HStack {
Text(String(format: "$%.2f", Ear[index].value))
.modifier(ValueModifier())
Text(Ear[index].code)
.modifier(CodeModifier())
}
.frame(width:270)
}
.modifier(RowFormatModifier())
.listRowBackground(Color("\(hour)").opacity(0.9))
.listRowSeparator(.hidden)
}
}
Section(header: Text("Nose")
.font(.title)
.fontWeight(.thin)) {
ForEach(Nose.indices, id: \.self) {index in
VStack {
VStack() {
Spacer()
Text(Nose[index].name)
.modifier(NameModifier())
}
HStack {
Text(String(format: "$%.2f", Nose[index].value))
.modifier(ValueModifier())
Text(Nose[index].code)
.modifier(CodeModifier())
}
.frame(width:270)
}
.frame(width: 330, height:95, alignment: .center)
.modifier(RowFormatModifier())
.listRowBackground(Color("18").opacity(0.9))
.listRowSeparator(.hidden)
}
}
The second section is using the Color("18") which is in reference to 1800 just for reference as well. I have custom colour for each hour and thus why I am using the Timeline view to update the variable
The hour variable would be updated by the Timelineview as:
TimelineView(.animation) {context in
let now = Date()
let hour = Calendar.current.component(.hour, from: now)
}
I am not sure and I have tried and failed to try to put this in a ViewModifier where I could update the list row colour.
struct ChangeRowColor: ViewModifier {
func body(content: Content) -> some View {
content
TimelineView(.animation) {context in
let now = Date()
let hour = Calendar.current.component(.hour, from: now)
}
.listRowBackground(Color("\(hour)").opacity(0.9))
}
I know this is wrong because it can't see the hour variable.
Any suggestions? Where should I call the timeline to determine the hour? I only want to do it once and pass this into the different sections and other views that are of similar format.
Thanks!
Using the new SwiftUI Charts framework, we can make a chart bigger than the visible screen and place it into a ScrollView to make it scrollable. Something like this:
var body : some View {
GeometryReader { proxy in
ScrollView(.horizontal, showsIndicators: false) {
Chart {
ForEach(data) { entry in
// ...
}
}
.frame(width: proxy.size.width * 2)
}
}
}
Does anybody know if it is possible to programmatically move the scroll to display a certain area of the chart?
I've tried using ScrollViewReader, setting the IDs at the x-axis labels, and trying to use the scrollTo function to navigate to any of those positions with no luck:
Chart {
/// ...
}
.chartXAxis {
AxisMarks(values: .stride(by: .day)) { value in
if let date : Date = value.as(Date.self) {
Text(date, style: .date)
.font(.footnote)
}
}
}
This cheesy workaround seems to do the trick. I put the chart in a ZStack with an HStack overlaying the chart. The HStack contains a bunch of invisible objects that conform to the Identifiable protocol. The quantity, ids, and positions of the invisible objects match the charted data.
Since the ZStack view now contains identifiable elements, ScrollViewReader works as expected.
import SwiftUI
import Charts
struct ChartData: Identifiable {
var day: Int
var value: Int
var id: String { "\(day)" }
}
struct ContentView: View {
#State var chartData = [ChartData]()
#State var scrollSpot = ""
let items = 200
let itemWidth: CGFloat = 30
var body: some View {
VStack {
ScrollViewReader { scrollPosition in
ScrollView(.horizontal) {
// Create a ZStack with an HStack overlaying the chart.
// The HStack consists of invisible items that conform to the
// identifible protocol to provide positions for programmatic
// scrolling to the named location.
ZStack {
// Create an invisible rectangle for each x axis data point
// in the chart.
HStack(spacing: 0) {
ForEach(chartData) { item in
Rectangle()
.fill(.clear)
// Setting maxWidth to .infinity here, combined
// with spacing:0 above, makes the rectangles
// expand to fill the frame specified for the
// chart below.
.frame(maxWidth: .infinity, maxHeight: 0)
// Here, set the rectangle's id to match the
// charted data.
.id(item.id)
}
}
Chart(chartData) {
BarMark(x: .value("Day", $0.day),
y: .value("Amount", $0.value),
width: 20)
}
.frame(width: CGFloat(items) * itemWidth, height: 300)
}
}
.padding()
.onChange(of: scrollSpot, perform: {x in
if (!x.isEmpty) {
scrollPosition.scrollTo(x)
scrollSpot = ""
}
})
}
.onAppear(perform: populateChart)
Button("Scroll") {
if let x = chartData.last?.id {
print("Scrolling to item \(x)")
scrollSpot = x
}
}
Spacer()
}
}
func populateChart() {
if !chartData.isEmpty { return }
for i in 0..<items {
chartData.append(ChartData(day: i, value: (i % 10) + 2))
}
}
}
IMHO this should work out of the SwiftUI box. Apple's comments for the initializer say it creates a chart composed of a series of identifiable marks. So... if the marks are identifiable, it is not a stretch to expect ScrollViewReader to work with the chart's marks.
But noooooo!
One would hope this is an oversight on Apple's part since the framework is new, and they will expose ids for chart marks in an upcoming release.
I am trying to recreate a layout similar to the Reminders app. Looking at it makes me think it was built with SwiftUI. I also believe Apple mentioned so in one of the WWDC videos (can't remember which one).
This above screenshot seems to be a List, with a LazyVGrid as the first View inside the List. Tapping on each of the items in the LazyVGrid, such as Today, Scheduled, All and Flagged, navigates to the relevant screen, which means they are all NavigationLinks. Also note that the LazyVGrid has 2 columns.
And then there is another section "My Lists" which has rows which look like regular list rows in a List with style .insetGrouped. Also, every item in this Section is a NavigationItem, and thus comes with the disclosure indicator on the right as usual. Recreating this is trivial, so it has been left out from the MRE.
I am having trouble recreating the first section, which has that LazyVGrid. I faced 3 problems (as mentioned in the image), of which I have been able to solve the first one only. The other two problems remain. I want to know if this MRE can be fixed, or is my entire approach incorrect.
I am including a minimum reproducible example below.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
RemindersView()
}
}
}
struct RemindersView: View {
private var columns: [GridItem] = [GridItem(.adaptive(minimum: 150))]
private var smartLists: [SmartList] = SmartList.sampleLists
var body: some View {
NavigationView {
List {
Section(header: Text("Using LazyVGrid")) {
grid
}
Section(header: Text("Using HStack")) {
hstack
}
}
.navigationTitle("Store")
}
.preferredColorScheme(.dark)
}
private var grid: some View {
LazyVGrid(columns: columns, spacing: 8) {
ForEach(smartLists) { smartList in
// This use of **ZStack with an EmptyView with opacity 0** is a hack being used to avoid the disclosure indicator on each item in the grid
ZStack(alignment: .leading) {
NavigationLink( destination: SmartListView(list: smartList)) {
EmptyView()
}
.opacity(0)
SmartListView(list: smartList)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
private var hstack: some View {
ScrollView(.horizontal) {
HStack {
ForEach(smartLists) { smartList in
NavigationLink(destination: SmartListView(list: smartList)) {
SmartListView(list: smartList)
}
.buttonStyle(.plain)
}
}
}
.listRowInsets(EdgeInsets())
.listRowBackground(Color.clear)
}
}
struct RemindersView_Previews: PreviewProvider {
static var previews: some View {
RemindersView()
}
}
struct SmartList: Identifiable {
var id: UUID = UUID()
var title: String
var count: Int
var icon: String
var iconColor: Color
static var sampleLists: [SmartList] {
let today = SmartList(title: "Today", count: 5, icon: "20.circle.fill", iconColor: .blue)
let scheduled = SmartList(title: "Scheduled", count: 12, icon: "calendar.circle.fill", iconColor: .red)
let all = SmartList(title: "All", count: 77, icon: "tray.circle.fill", iconColor: .gray)
let flagged = SmartList(title: "Flagged", count: 5, icon: "flag.circle.fill", iconColor: .orange)
return [today, scheduled, all, flagged]
}
}
struct SmartListView: View {
var list: SmartList
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(alignment: .center) {
Image(systemName: list.icon)
.renderingMode(.original)
.font(.title)
.foregroundColor(list.iconColor)
Spacer()
Text("\(list.count)")
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
.padding(.horizontal, 8)
}
Text(list.title)
.font(.system(.headline, design: .rounded))
.foregroundColor(.secondary)
}
.padding(8)
.background(
RoundedRectangle(cornerRadius: 12)
.foregroundColor(.gray.opacity(0.25))
)
.padding(2)
.frame(minWidth: 150)
}
}
EDIT 1: Adding video demo of what editing the dynamic Grid looks like and how the Grid has dynamic grid items (via the Edit button at the top right): https://imgur.com/a/TV0kifY
I am creating a reusable gallery view for an app and am having difficulties when any picture is tapped it suppose to become full screen but only the first picture in the array is shown every time no matter the picture tapped. Below is my code, thanks.
import SwiftUI
struct ReusableGalleryView: View {
let greenappData: GreenAppNews
let gridLayout: [GridItem] = Array(repeating: GridItem(.flexible()), count: 3)
#State private var fullscreen = false
#State private var isPresented = false
var body: some View {
VStack{
ScrollView{
LazyVGrid(columns: gridLayout, spacing: 3) {
ForEach(greenappData.GreenAppGallery, id: \.self) { item in
Image(item)
.resizable()
.frame(width: UIScreen.main.bounds.width/3, height: 150)
.onTapGesture {
self.isPresented.toggle()
print(" tapping number")
}
.fullScreenCover(isPresented: $isPresented) {
FullScreenModalView( imageFiller: item)
}
.background(Color.gray.opacity(0.5))
}
}
.padding(.horizontal)
}
}
}
}
This is an example of the json data:
{
"id" : "1",
"GreenAppGallery" : [
"Picture-1",
"Picture-2",
"Picture-3",
"Picture-4",
"Picture-5",
"Picture-6",
"Picture-7",
"Picture-8",
"Picture-9",
"Picture-10"
]
},
fullScreenCover, like sheet tends to create this type of behavior in iOS 14 when using isPresented:.
To fix it, you can change to the fullScreenCover(item: ) form.
Not having all of your code, I'm not able to give you an exact version of what it'll look like, but the gist is this:
Remove your isPresented variable
Replace it with a presentedItem variable that will be an optional. Probably a datatype that is in your gallery. Note that it has to conform to Identifiable (meaning it has to have an id property).
Instead of toggling isPresented, set presentedItem to item
Use fullScreenCover(item: ) { presentedItem in FullScreenModalView( imageFiller: presentedItem) } and pass it your presentedItem variable
Move the fullScreenCover so that it's attached to the ForEach loop rather than the Image
Using this system, you should see it respond to the correct item.
Here's another one of my answers that covers this with sheet: #State var not updated as expected in LazyVGrid
When I run a Picker Code in the Simulator or the Canvas, the Picker goes always back to the first option with an animation or just freezes. This happens since last Thursday/Friday. So I checked some old simple code, where it worked before that and it doesn't work for me there, too.
This is the simple old Code. It doesn't work anymore in beta 3, 4 and 5.
struct PickerView : View {
#State var selectedOptionIndex = 0
var body: some View {
VStack {
Text("Option: \(selectedOptionIndex)")
Picker(selection: $selectedOptionIndex, label: Text("")) {
Text("Option 1")
Text("Option 2")
Text("Option 3")
}
}
}
}
In my newer code, I used #ObservedObject, but also here it doesn't work.
Also I don't get any errors and it builds and runs.
Thank you for any pointers.
----EDIT----- Please look at the answer first
After the help, that I could use the .tag() behind all Text()like Text("Option 1").tag(), it now takes the initial value and updates it inside the view. If I use #ObservedObject like here:
struct PickerView: View {
#ObservedObject var data: Model
let width: CGFloat
let height: CGFloat
var body: some View {
VStack(alignment: .leading) {
Picker(selection: $data.exercise, label: Text("select exercise")) {
ForEach(data.exercises, id: \.self) { exercise in
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
}
}
.frame(width: width, height: (height/2), alignment: .center)
}
}
}
}
Unfortunately it doesn't reflect changes on the value, if I make these changes in another view, one navigationlink further. And also it doesn't seem to work with the my code above, where I use firstIndex(of: exercise)
---EDIT---
Now the code above works if I change
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise))
into
Text("\(exercise)").tag(self.data.exercises.firstIndex(of: exercise)!)
because it couldn't work with an optional.
The answer summarized:
With the .tag() behind the Options it works. It would look like following:
Picker(selection: $selectedOptionIndex, label: Text("")) {
ForEach(1...3) { index in
Text("Option \(index)").tag(index)
}
}
If you use a range of Objects it could look like this:
Picker(selection: $data.exercises, label: Text("")) {
ForEach(0..<data.exercises.count) { index in
Text("\(data.exercises[index])").tag(index)
}
}
I am not sure if it is intended, that .tag() is needed to be used here, but it's at least a workaround.
I found a way to simplify the code a bit without the need of operating on indicies and tags.
At first, make sure to conform your model to Identifiable protocol like this (this is actually a key part, as it enables SwiftUI to differentiate elements):
public enum EditScheduleMode: String, CaseIterable, Identifiable {
case closeSchedule
case openSchedule
public var id: EditScheduleMode { self }
var localizedTitle: String { ... }
}
Then you can declare viewModel like this:
public class EditScheduleViewModel: ObservableObject {
#Published public var editScheduleMode = EditScheduleMode.closeSchedule
public let modes = EditScheduleMode.allCases
}
and UI:
struct ModeSelectionView: View {
private let elements: [EditScheduleMode]
#Binding private var selectedElement: EditScheduleMode
internal init?(elements: [EditScheduleMode],
selectedElement: Binding<EditScheduleMode>) {
self.elements = elements
_selectedElement = selectedElement
}
internal var body: some View {
VStack {
Picker("", selection: $selectedElement) {
ForEach(elements) { element in
Text(element.localizedTitle)
}
}
.pickerStyle(.segmented)
}
}
}
With all of those you can create a view like this:
ModeSelectionView(elements: viewModel.modes, selectedElement: $viewModel.editScheduleMode)