I have a view in my Swift5 project where I use SwiftUI.
It's a List View.
Is there any way to add or remove a VStack from the list depending on a variable's data?
I can't place any logic in that part of the code so now I'm struggling.
List {
VStack {
Text(txt)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
}
VStack { // this view should be displayed or hidden if the data variable is 0
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .bottomLeading)
}.onReceive(imageLoader.didChange) { data in
self.image = UIImage(data: data) ?? UIImage()
// here I check if there is any kind of data
if data.count == 0 {
print("Data is 0 so there is no image") // in this case I don't need the second VStack
} else {
print("Data is not 0 so there is an image") // in this case I need the second VStack
}
}
}
I've never tried to learn SwiftUI, because I got used to Swift5 so I don't have any kind of SwiftUI knowledge.
Heres a simple view, if you click on the button the first item inside the List disappears and if you click it again it shows up again:
To achive this I use a #State Variable. Changing the State of my view forces my view to reload. There are many nice articles online that describe the functionality of #State.
import SwiftUI
struct ContentView: View {
#State private var show = true
var body: some View {
VStack {
List {
if show {
Text("Text1")
}
Text("Text2")
Text("Text3")
}
Button(action: {
self.show.toggle()
}) {
Text("Hide Text1")
}
}
}
}
You can achive the same in your example:
if data.count == 0 {
self.show = false
} else {
self.show = true
}
you can use the show variable that you set based on the data to display a view.
if show {
Text("Text1") // Or whatever view you like
}
Related
I want to have an alert style view that can include an icon image at the top along with the title.
alert(isPresented:content:) used with Alert does not seem to support adding images in any way. However, other than that limitation, it functions as I want my alert to function. It covers the status bar and blurs the main view as the background for the alert.
I attempted to use fullScreenCover(isPresented:onDismiss:content:) instead, but this approach does not behave like an alert as far as covering up EVERYTHING including the status bar, and blurring the main view as the background.
I have tried this so far, with the following result. One of the reasons I want it to behave like a normal Alert is so that the content can scroll without overlapping the clock time.
struct AlertView: View {
#EnvironmentObject var dataProvider: DataProvider
var alert: Watch.AlertMessage
var body: some View {
ScrollView {
Image("IconAlert")
.resizable()
.renderingMode(.template)
.aspectRatio(contentMode: .fit)
.frame(width: 40, height: 40)
.foregroundColor(.accentColor)
Text("Test Title")
.bold()
Text("Test Description")
Button("DISMISS") { print("dismiss") }
}
.padding()
.ignoresSafeArea()
.navigationBarHidden(true)
}
}
Try this this sample custom alert. It may give you some great approach(Code is below the image):
struct CustomAlert: View {
#State var isClicked = false
var body: some View {
ZStack {
Color.orange.edgesIgnoringSafeArea(.all)
Button("Show Alert") {
withAnimation {
isClicked.toggle()
}
}
ZStack {
if isClicked {
RoundedRectangle(cornerRadius: 20)
.fill(.thickMaterial)
.frame(width: 250, height: 250)
.transition(.scale)
VStack {
Image("Swift")
.resizable()
.frame(width: 150, height: 150)
.mask(RoundedRectangle(cornerRadius: 15))
Button("Dismiss") {
withAnimation {
isClicked.toggle()
}
}
.foregroundColor(.red)
}
.transition(.scale)
}
}
}
}
}
I can't find a solution to change my background color view, I tried a lot of options and nothing works.
I'm trying solutions but the isn't changing
There is my struct of the code:
struct ContentView: View {
var body: some View {
VStack {
Text("Trying ColorView")
.font(.largeTitle)
.bold()
Button("ColorView") {
}
}
.accentColor(Color.black)
}
}
First of all you have already mistakes in your posted code above, your XCode should normally tell you that.
Which view you want to change..?
This might be a solution... You can change it like you need it.
struct testViewTwo: View {
var body: some View {
NavigationView {
VStack {
VStack(spacing: -15) {
HStack {
HStack {
Text("Hello World")
}.background(Color.blue)
}.foregroundColor(Color.white)
}
}.background(Image("Background"))
}
}
}
You change a background color with the background modifier.
If you want to change the background color of the whole view, you should create a ZStack that spans the whole view with a background modifier.
ZStack {
...
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.background(Color(.blue)
You can simply use Color("Green") to change the color. Here's a sample.
var body: some View {
NavigationView{
VStack {
VStack(spacing: 15){
HStack {
Color("Green")
.edgesIgnoringSafeArea(.top)
}
}
}
}
}
I have a list that builds up with a NavigationLink based on the members of an array. When you click on the list item, the next view loads up to the right displaying the data for that particular item.
When you delete the item, the next one gets displayed. However, when you delete all the items in the list, the last item's data remains left behind on the loaded view. I tried to look at the selected item being nil, or some other form of checking whether something is selected, or the list has at least one item, but I can't figure out how check all this and unload the view.
Here's the code:
#State var selectedNoteId: UUID?
NavigationView {
List(data.notes) { note in
NavigationLink(
destination: NoteView(note: note),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!).font(.body).fontWeight(.bold)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 10)
}
}
.listStyle(InsetListStyle())
.frame(minWidth: 250, maxWidth: .infinity)
Text("Select a note or create a new one...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.navigationTitle("A title")
How do I unload NoteView and go back to displaying the Text("Select a note or create a new one...")
Thanks for the help!
EDITED:
this is how I remove data:
func removeNote() {
if let selection = selectedNoteId,
let selectionIndex = data.notes.firstIndex(where: { $0.id == selection }) {
//print("DEBUG: delete item: \(selectionIndex)")
data.notes.remove(at: selectionIndex)
}
}
You can wrap whole list with if
NavigationView {
if !data.notes.isEmpty {
List(data.notes) { note in
NavigationLink(
destination: NoteView(note: note),
tag: note.id,
selection: $selectedNoteId
) {
VStack(alignment: .leading) {
Text(note.text.components(separatedBy: NSCharacterSet.newlines).first!).font(.body).fontWeight(.bold)
Text(note.dateText).font(.body).fontWeight(.light)
}
.padding(.vertical, 10)
}
}
.frame(minWidth: 250, maxWidth: .infinity)
} else {
List(data.notes) { _ in EmptyView() }
}
Text("Select a note or create a new one...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.listStyle(InsetListStyle())
.navigationTitle("A title")
.onAppear {
DispatchQueue.main.async {
selectedNoteId = data.notes.first?.id
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
data.notes.removeAll()
}
}
When notes is empty, content of list isn't being called. Looks like macOS is pretty buggy by not dismissing old navigation link
Would something like this work? Im not sure how you are actually removing data when the user deletes it.
List(data.notes) { note in
if data.notes.isEmpty {
navlink
} else {
Text("Select a note or create a new one...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
So, I found a cleaner way. Add an extension:
extension View {
#ViewBuilder func hidden(_ shouldHide: Bool) -> some View {
switch shouldHide {
case true: self.hidden()
case false: self
}
}
}
Then in NoteView call:
.hidden(data.notes.isEmpty)
And when data is empty the view will go away.
It's an ugly hack, I guess, but somehow the last view is left behind when there are not items in the list, and I can't find any other way to force it to unload.
I have a problem. I have a main view to build but I cannot manage how to do it like it is on screen. I tried to use 3 HStack's in VStack, but cannt manage what to do next, use frame or overlay, completelly newbie in SwiftUI. Menu items will contains images and text below. Does anyone can help me with that. Greetings
enter image description here
Here is a possible solution. Using VStack as main wrapper then HStack as rows where every Item tries to have to maximum width.
struct ContentView: View {
var body: some View {
VStack(spacing: 0) {
HStack(spacing: 0) {
ItemView()
ItemView()
}
HStack(spacing: 0) {
ItemView()
ItemView()
}
HStack(spacing: 0) {
ItemView()
ItemView()
}
}
}
}
struct ItemView : View {
var body : some View {
VStack {
Spacer()
Image(systemName: "airplane")
.resizable()
.frame(width: 50, height: 50)
Spacer()
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.border(Color.black)
}
}
I'm attempting to create a master/detail view on macOS with SwiftUI. When the master/detail view first renders, I'd like it to immediately "highlight" / "navigate to" its first entry.
In other words, I'd like to immediately render the following: master/detail first row highlighted
I'm using NavigationView and NavigationLink on macOS to render the master/detail view:
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: Text("detail-1").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Text("link-1")
}
NavigationLink(destination: Text("detail-2").frame(maxWidth: .infinity, maxHeight: .infinity)) {
Text("link-2")
}
}
}
}
}
I've tried using both the isActive and the tag / selection options provided by NavigationLink with no luck. What might I be missing here? Is there a way to force focus on the first master/detail element using SwiftUI?
I came across this problem recently and after being stuck at the same point I found Apple's tutorial which shows that you don't use NavigationLink on macOS.
Instead you just create a NavigationView with a List and a DetailView. Then you can bind the List's selection and it works properly.
There still seems to be a bug with the highlight. The workaround is setting the selection in the next run loop after the NavigationView has appeared. :/
Here's a complete example:
enum DetailContent: Int, CaseIterable, Hashable {
case first, second, third
}
extension DetailContent: Identifiable {
var id: Int { rawValue }
}
struct DetailView: View {
#Binding var content: DetailContent?
var body: some View {
VStack {
Text("\(content?.rawValue ?? -1)")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct MainView: View {
#State var detailContent: DetailContent?
var body: some View {
NavigationView {
List(selection: $detailContent) {
Section(header: Text("Section")) {
ForEach(DetailContent.allCases) { item in
Text("\(item.rawValue)")
.tag(item)
}
}
}
.frame(minWidth: 250, maxWidth: 350)
.listStyle(SidebarListStyle())
if detailContent != nil {
DetailView(content: $detailContent)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.onAppear {
DispatchQueue.main.async {
self.detailContent = DetailContent.allCases.randomElement()
}
}
}
}