I have a Zstack within ContentView in xcode I'm attempting to add a background image to. This image is intended to serve as the background for the entire screen, but it does not show up. The image is saved in xcassets with the correct label for the code below. This has been double, no triple-checked.
My code:
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#State var phoneNumberCompleted = false;
#State var phoneNumber = ""
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View
{
ZStack
{
NavigationView
{
Form
{
Section(header: Text("Account Credentials"))
{
TextField("Phone Number", text: $phoneNumber)
if phoneNumberCompleted
{
Button(action: {
}) {
HStack
{
Spacer()
Text("Log in")
.font(.system(size: 20))
.padding(2)
.background(Color.red)
.foregroundColor(Color.white)
.cornerRadius(10)
.shadow(color: Color.gray, radius: 2)
Spacer()
}
}
}
}
Section(header: Text("ABOUT APP"))
{
HStack
{
Text("Version")
Spacer()
Text("0.0.1")
}
}
}
.navigationBarTitle("Welcome to Mapwork")
}
}.background(Image("bg"))
.opacity(100)
}
}
I'm wondering if I am using the correct approach or if something is wrong here. Any help appreciated.
First of all you need to add the code below to make the background clear:
init() {
UITableView.appearance().backgroundColor = .clear
}
Then you can add your background without a problem and here it is the result:
.navigationBarTitle("Welcome to Mapwork")
.background(Image("image1"))
Note: I don't know why are you using opacity anyway, for your information the opacity value it must be between 0 and 1.
Related
I'm working on creating a view which uses matchedGeometryEffect, embedded within a NavigationView, and when presenting the second view another NavigationView.
The animation "works" and it matches correctly, however when it toggles from view to view it happens to swing as if unwinding from the navigation stack.
However, if I comment out the NavigationView on the secondary view the matched geometry works correctly.
I am working on iOS 14.0 and above.
Sample code
Model / Mock Data
struct Model: Identifiable {
let id = UUID().uuidString
let icon: String
let title: String
let account: String
let colour: Color
}
let mockItems: [Model] = [
Model(title: "Test title 1", colour: .gray),
Model(title: "Test title 2", colour: .blue),
Model(title: "Test title 3", colour: .purple)
...
]
Card View
struct CardView: View {
let item: Model
var body: some View {
VStack {
Text(item.title)
.font(.title3)
.fontWeight(.heavy)
}
.padding()
.frame(maxWidth: .infinity, minHeight: 100, alignment: .leading)
.background(item.colour)
.foregroundColor(.white)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
Secondary / Detail View
struct DetailView: View {
#Binding var isShowingDetail: Bool
let item: Model
let animation: Namespace.ID
var body: some View {
NavigationView { // <--- comment out here and it works
VStack {
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
withAnimation { isShowingDetail = false }
}
ScrollView(.vertical, showsIndicators: false) {
Text("Lorem ipsum dolor...")
}
}
.padding(.horizontal)
.navigationBarTitleDisplayMode(.inline)
}
.navigationViewStyle(.stack)
}
}
Primary View
struct ListView: View {
#State private var selectedCard: Model?
#State private var isShowingCard: Bool = false
#Namespace var animation
var body: some View {
ZStack {
NavigationView {
ScrollView(.vertical, showsIndicators: false) {
ForEach(mockItems) { item in
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
withAnimation {
selectedCard = item
isShowingCard = true
}
}
}
}
.navigationTitle("Test title")
}
.padding(.horizontal)
.navigationViewStyle(.stack)
// show detail view
if let selectedCard = selectedCard, isShowingCard {
DetailView(
isShowingDetail: $isShowingCard,
item: selectedCard,
animation: animation
)
}
}
}
}
Video examples
With NavigationView in DetailView
Without NavigationView in DetailView
Ignore the list view still visible
You don't need second NavigationView (actually I don't see links at all, so necessity of the first one is also under question). Anyway we can just change the layout order and put everything into one NavigationView, like below.
Tested with Xcode 13.4 / iOS 15.5
struct ListView: View {
#State private var selectedCard: Model?
#State private var isShowingCard: Bool = false
#Namespace var animation
var body: some View {
NavigationView {
ZStack { // container !!
if let selectedCard = selectedCard, isShowingCard {
DetailView(
isShowingDetail: $isShowingCard,
item: selectedCard,
animation: animation
)
} else {
ScrollView(.vertical, showsIndicators: false) {
ForEach(mockItems) { item in
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
selectedCard = item
isShowingCard = true
}
}
}
.navigationTitle("Test title")
}
}
.padding(.horizontal)
.navigationViewStyle(.stack)
.animation(.default, value: isShowingCard) // << animated here !!
}
}
}
struct DetailView: View {
#Binding var isShowingDetail: Bool
let item: Model
let animation: Namespace.ID
var body: some View {
VStack {
CardView(item: item)
.matchedGeometryEffect(id: item.id, in: animation)
.onTapGesture {
isShowingDetail = false
}
ScrollView(.vertical, showsIndicators: false) {
Text("Lorem ipsum dolor...")
}
}
.padding(.horizontal)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}
}
Test module is here
//
// ContentView.swift
// DemoProject
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
private var gridItemLayout = [GridItem(.adaptive(minimum: 100))]
#StateObject private var viewModel = HomeViewModel()
var body: some View {
GeometryReader { geometry in
NavigationView {
ScrollView {
LazyVGrid(columns: gridItemLayout, spacing: 20) {
ForEach(viewModel.results, id: \.self) {
let viewModel = ResultVM(model: $0)
NavigationLink(destination: {
DetailView()
}, label: {
SearchResultRow(resultVM: viewModel)
})
}
}
}
}
.onAppear(perform: {
viewModel.performSearch()
})
}
}
}
struct SearchResultRow: View {
let resultVM: ResultVM
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 16).fill(.yellow)
.frame(maxWidth: .infinity).aspectRatio(1, contentMode: .fit)
.overlay(Text(resultVM.trackName))
.onTapGesture {
}
}.padding()
.background(Color.red)
}
}
I want to navigate to detailed view on click on each Cell, I tried navigation but its not clicking.
DetailView
import SwiftUI
struct DetailView: View {
var body: some View {
Text("Hello World")
}
}
To make your sub grid clickable and leads to another view try this:
ForEach(viewModel.results, id: \.self) {
let viewModel = ResultVM(model: $0)
NavigationLink {
//your DetailView()
} label: {
//this is the design for your navigation button
SearchResultRow(resultVM: viewModel)
}
}
The tap gesture blocks NavigationLink (which is actually a button, so also works onTap), so the fix is just remove
var body: some View {
HStack {
RoundedRectangle(cornerRadius: 16).fill(.yellow)
.frame(maxWidth: .infinity).aspectRatio(1, contentMode: .fit)
.overlay(Text(resultVM.trackName))
// .onTapGesture { // << this conflict !!
// }
Tested with Xcode 13.4 / iOS 15.5
*Note: if you need both, then use .simultaneousGesture(TapGesture().onEnded {})
So I'm trying to make a list where the background of each element is an image that completely fills the element and I'm struggling to get it to completely fill the area.
here's some simple example code showing what I have so far.
struct Event {
var description:String = "description"
var title:String = "Title"
var view:Image
}
struct RowView: View {
#State var event:Event
var body: some View {
VStack {
Text(event.title)
.font(.headline)
Text(event.description)
.font(.subheadline)
}
}
}
struct ContentView: View {
#State var events:[Event] = [Event(view: Image("blue")),Event( view: Image("red")),Event( view: Image("green"))]
#State private var editMode = EditMode.inactive
var body: some View {
NavigationView {
List() {
ForEach(events.indices, id: \.self) { elementID in
NavigationLink(
destination: RowView(event: events[elementID])) {
RowView(event: events[elementID])
}
.background(events[elementID].view)
.clipped()
}
}
.listRowInsets(.init(top: 0, leading: 0, bottom: 0, trailing: 0))
.navigationTitle("Events")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
This is what's produced by the code as you can see there is a border around the background image still.
I don't know what your images are, but seems that should not be important. Here is a demo on replicated code using colors.
Tested with Xcode 12.1 / iOS 14.1
Modified parts (important highlighted in comments)
struct Event {
var description:String = "description"
var title:String = "Title"
var view:Color
}
struct ContentView: View {
#State var events:[Event] = [Event(view: .blue),Event( view: .red),Event( view: .green)]
#State private var editMode = EditMode.inactive
var body: some View {
NavigationView {
List() {
ForEach(events.indices, id: \.self) { elementID in
events[elementID].view.overlay( // << this !!
NavigationLink(
destination: RowView(event: events[elementID])) {
RowView(event: events[elementID])
})
}
.listRowInsets(EdgeInsets()) // << this !!
}
.listStyle(PlainListStyle()) // << and this !!
.navigationTitle("Events")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
This works:
struct RowView: View {
#State var event:Event
var body: some View {
VStack(alignment: .leading) {
Text(event.title)
.font(.headline)
Text(event.description)
.font(.subheadline)
}
.padding(10)
.foregroundColor(.yellow)
}
}
struct ContentView: View {
#State var events:[Event] = [Event(view: Image("blue")),Event( view: Image("red")),Event( view: Image("green"))]
#State private var editMode = EditMode.inactive
var body: some View {
NavigationView {
List() {
ForEach(events.indices, id: \.self) { elementID in
NavigationLink(
destination: RowView(event: events[elementID])) {
RowView(event: events[elementID])
}
.background(events[elementID].view
.resizable()
.aspectRatio(contentMode: .fill)
)
.clipped()
}
.listRowInsets(EdgeInsets())
}
.listStyle(PlainListStyle())
.navigationTitle("Events")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
This is a derivative of Asperi's answer, so you can accept his answer, but this works with images, and no overlay. Tested on Xcode 12.1, IOS 14.1.
Here is an image where I grabbed arbitrary sized images for "red", "green", and "blue" from Goggle. I took the liberty of changing the row text to yellow so it would show up better.
For my app, I have a welcome screen that intro's what the app does and allows the user to create their first item. When the user clicks the button I'd like to dismiss the 'welcomeScreen' sheet and and then launch the 'newRemindr' sheet.
I tried to achieve this by creating an observable object with an 'addNewTrigger' boolean set to false. When I click the Add New Reminder button on the welcomeScreen, the button's action causes the welcomeScreen to dismiss and toggles the 'addNewTrigger' boolean to True. (I've verified this is working with Print Statements). However content view is listening to that same observed object to launch the 'newRemindr' sheet but that action doesn't seem to be working.
Can somebody please take a look at the code and see where I am going wrong? Or suggest an alternative that can provide the type of functionality.
I really appreciate all the help. Thanks!
Code Below...
welcomeScreen:
import SwiftUI
import Combine
struct welcomeScreen: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var addNewReminder = showAddScreen()
var body: some View {
NavigationView {
ZStack (alignment: .center) {
LinearGradient(gradient: Gradient(colors: [Color.white, Color.white, Color.gray]), startPoint: .top, endPoint: .bottom)
.edgesIgnoringSafeArea(.all)
Image("Ellipse2")
.offset(y: -475)
VStack {
Spacer()
Text("Welcome to")
.foregroundColor(.white)
.fontWeight(.bold)
Image("RemindrLogoWhite")
Spacer()
Text("What is remindr?")
.font(.title)
.fontWeight(.bold)
.padding(.bottom, 25)
Text("Remindr is a simple app designed to help you schedule random reminders with the goal of clearing your mind.\n\nRemind yourself to check in with your body, set up positive affirmations, set your intentions; Whatever it is, the power is up to you.")
.padding(.horizontal, 25)
.padding(.bottom, 25)
Text("Click below to get started:")
.fontWeight(.bold)
// Add New Reminder Button
Button(action: {
self.mode.wrappedValue.dismiss()
print("Add Reminder Button from Welcome Screen is Tapped")
self.addNewReminder.addNewTrigger.toggle()
print("var addNewTrigger has been changed to \(self.addNewReminder.addNewTrigger)")
}) {
Image("addButton")
.renderingMode(.original)
}.padding(.bottom, 25)
Spacer()
} .frame(maxWidth: UIScreen.main.bounds.width,
maxHeight: UIScreen.main.bounds.height)
}
.navigationBarTitle(Text(""), displayMode: .automatic)
.navigationBarItems(trailing: Button(action: {
self.mode.wrappedValue.dismiss()
}, label: {
Image(systemName: "xmark")
.foregroundColor(.white)
}))
}
}
}
ContentView:
import SwiftUI
import CoreData
class showAddScreen: ObservableObject {
#Published var addNewTrigger = false
}
struct ContentView: View {
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: ReminderEntity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ReminderEntity.dateCreated, ascending: false)])
var reminder: FetchedResults<ReminderEntity>
// Sheet Control
#ObservedObject var addNewReminder = showAddScreen()
//#State private var showingAddScreen = false
#State var showWelcomeScreen = false
let emojiList = EmojiList()
//Toggle Control
#State var notifyOn = true
// Save Items Function
func saveItems() {
do {
try moc.save()
} catch {
print(error)
}
}
// Delete Item Function
func deleteItem(indexSet: IndexSet) {
let source = indexSet.first!
let listItem = reminder[source]
moc.delete(listItem)
}
// View Controller
var body: some View {
VStack {
NavigationView {
ZStack (alignment: .top) {
// List View
List {
ForEach(reminder, id: \.self) { notification in
NavigationLink(destination: editRemindr(reminder: notification,
notifyOn: notification.notifyOn,
emojiChoice: Int(notification.emojiChoice),
notification: notification.notification ?? "unknown",
notes: notification.notes ?? "unknown")) {
// Text within List View
HStack {
// MARK: TODO
//Toggle("NotifyOn", isOn: self.$notifyOn)
// .labelsHidden() // Hides the label/title
Text("\(self.emojiList.emojis[Int(notification.emojiChoice)]) \(notification.notification!)")
}
}
}
.onDelete(perform: deleteItem)
}.lineLimit(1)
// Navigation Items
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(
leading:
HStack {
Button(action: {
self.showWelcomeScreen.toggle()
}) {
Image(systemName: "info.circle.fill")
.font(.system(size: 24, weight: .regular))
}.foregroundColor(.gray)
// Positioning Remindr Logo on Navigation
Image("remindrLogoSmall")
.resizable()
.aspectRatio(contentMode: .fit)
//.frame(width: 60, height: 60, alignment: .center)
.padding(.leading, 83)
.padding(.top, -10)
},
// Global Settings Navigation Item
trailing: NavigationLink(destination: globalSettings()){
Image("settings")
.font(Font.title.weight(.ultraLight))
}.foregroundColor(.gray)
)
// Add New Reminder Button
VStack {
Spacer()
Button(action: { self.addNewReminder.addNewTrigger.toggle()
}) {
Image("addButton")
.renderingMode(.original)
}
.sheet(isPresented: $addNewReminder.addNewTrigger) {
newRemindr().environment(\.managedObjectContext, self.moc)
}
}
}
} .sheet(isPresented: $showWelcomeScreen) {
welcomeScreen()
}
}
}
}
First what I see is you use different observable objects in both views, but should use same, so changes made in one view be available for second view as well.
Se here is a way to solve this
struct welcomeScreen: View {
#Environment(\.presentationMode) var mode: Binding<PresentationMode>
#ObservedObject var addNewReminder: showAddScreen // << declare to be injected
// ... other code
and in ContentView
} .sheet(isPresented: $showWelcomeScreen) {
welcomeScreen(addNewReminder: self.addNewReminder) // << inject !!
}
Alternate: you can remove addNewReminder from welcomeScreen and work with it only in ContentView by activating on welcome sheet dismiss, like
} .sheet(isPresented: $showWelcomeScreen, onDismiss: {
// it is better to show second sheet with delay to give chance
// for first one to animate closing to the end
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.addNewReminder.addNewTrigger.toggle()
}
}
) {
welcomeScreen()
}
i have made an app in SwiftUI where you can create different classes. The app saves this in an array. I have a textfield and a button in the same view as the scrollview that displays the array. This works perfectly fine and I can easily add new classes. Now I made a new view with a text field and a button. This view can be viewed by pressing a button in the nav bar. It uses the exact same function as the other view, but in the other view adding a item to the array works, in this view it doesn't work. I hope you understand my problem and can help me.
Thank You.
This is the file where I store the array:
import Foundation
import SwiftUI
import Combine
struct Class: Identifiable {
var name: String
var id = UUID()
}
class ClassStore: ObservableObject {
#Published var classes = [Class]()
}
This is the view with the button + textfield that works and the scrollview that displays the array:
import SwiftUI
struct ContentView: View {
#State var showNewClass = false
#ObservedObject var classStore = ClassStore()
#State var newClass: String = ""
#State var toDoColor: Color = Color.pink
func addNewClass() {
classStore.classes.append(
Class(name: newClass)
)
newClass = ""
}
var body: some View {
NavigationView {
VStack {
HStack {
TextField("New Todo", text: $newClass)
Image(systemName: "app.fill")
.foregroundColor(Color.pink)
.padding(.horizontal, 3)
Image(systemName: "books.vertical")
.padding(.horizontal, 3)
if newClass == "" {
Text("Add!")
.foregroundColor(Color.gray)
} else {
Button(action: {
addNewClass()
}) {
Text("Add!")
}
}
}.padding()
ScrollView {
ForEach(self.classStore.classes) { name in
Text(name.name)
}
}
.navigationBarTitle(Text("Schulnoten"))
.navigationBarItems(trailing:
Button(action: {
self.showNewClass.toggle()
}) {
Text("New Class")
}
.sheet(isPresented: $showNewClass) {
NewClass(isPresented: $showNewClass)
})
}
}
}
}
And this is the new view I created, the button and the textfield have the exact same code, but somehow this doesn't work:
import SwiftUI
struct NewClass: View {
#Binding var isPresented: Bool
#State var className: String = ""
#ObservedObject var classStore = ClassStore()
func addNewClass() {
classStore.classes.append(
Class(name: className)
)
}
var body: some View {
VStack {
HStack {
Text("New Class")
.font(.title)
.fontWeight(.semibold)
Spacer()
}
TextField("Name of the class", text: $className)
.padding()
.background(Color.gray)
.cornerRadius(5)
.padding(.vertical)
Button(action: {
addNewClass()
self.isPresented.toggle()
}) {
HStack {
Text("Safe")
.foregroundColor(Color.white)
.fontWeight(.bold)
.font(.system(size: 20))
}
.padding()
.frame(width: 380, height: 60)
.background(Color.blue)
.cornerRadius(5)
}
Spacer()
}.padding()
}
}
Sorry if my English is not that good. I'm not a native speaker.
I assume you should pass same class store from parent view into sheet, ie
struct NewClass: View {
#Binding var isPresented: Bool
#State var className: String = ""
#ObservedObject var classStore: ClassStore // << expect external
and in ContentView
.sheet(isPresented: $showNewClass) {
NewClass(isPresented: $showNewClass, classStore: self.classStore) // << here !!
})