SwiftUI View Controls - swiftui

I'm trying to use .alert and .onAppear for one of my views, and I want .onAppear to be called first, because .alert is dependent on a variable in .onAppear. Any way to specifically order ui controls?

SwiftUI provides a declarative user interface rather than the more imperative paradigm used in UIKit. So instead of using a sequence of commands to modify state and views separately, a SwiftUI view is defined as a function of its state.
So in this case the order of .onAppear and .alert does not change the presentation of the alert.
struct Demo: View {
#State var isAlertPresented = false
var body: some View {
Text("Hello, World!")
.onAppear() {
self.isAlertPresented = true
}
.alert(isPresented: self.$isAlertPresented) {
Alert(title: Text("Alert!"))
}
}
}
In the example above, when the Text view appears, .isAlertPresented changes to true. Separately, an Alert is added that will display whenever .isAlertPresented is true.
The example below has the same result, even changing the order of the two ViewModifiers.
struct Demo: View {
#State var isAlertPresented = false
var body: some View {
Text("Hello, World!")
.alert(isPresented: self.$isAlertPresented) {
Alert(title: Text("Alert!"))
}
.onAppear() {
self.isAlertPresented = true
}
}
}

Related

Non-deprecated way to call NavigationLink on Buttons

This is the old way of calling NavigationLink on Buttons
struct ContentView: View {
#State private var selection: String? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: View1(), tag: "tag1", selection: $selection) {
EmptyView()
}
NavigationLink(destination: NotView1(), tag: "tag2", selection: $selection) {
EmptyView()
}
Button("Do work then go to View1") {
// do some work that takes about 1 second
mySleepFunctionToSleepOneSecond()
selection = "tag1"
}
Button("Instantly go to NotView1") {
selection = "tag2"
}
}
.navigationTitle("Navigation")
}
}
}
This code works perfectly. It can go to different View targets depending on which button is clicked. Not only that, it guarantees all work is done BEFORE navigating to the target view. However, the only issue is that 'init(destination:tag:selection:label:)' was deprecated in iOS 16.0: use NavigationLink(value:label:) inside a List within a NavigationStack or NavigationSplitView
I get NavigationStack is awesome and such. But how can I translate the code to use the new NavigationStack + NavigationLink. Especially, how can I make sure work is done Before navigation?
Using new NavigationStack and its path property you can do much more. Your example will be transformed to
struct ContentView: View {
#State private var path = [String]()
var body: some View {
NavigationStack(path: $path) {
VStack {
Button("Do work then go to View1") {
// do some work that takes about 1 second
mySleepFunctionToSleepOneSecond()
path.append("tag1")
}
Button("Instantly go to NotView1") {
path.append("tag2")
}
}
.navigationTitle("Navigation")
.navigationDestination(for: String.self) { route in
switch route {
case "tag1":
EmptyView()
case "tag2":
EmptyView()
default:
EmptyView()
}
}
}
}
}
Check this video. There you can find more use cases.
For using non deprecated and after doing some work if we want to go to next view or in anyview there is something called ".navigationDestination". Let's see that using simple example.
#State var bool : Bool = false
var body: some View {
NavigationStack {
VStack {
Text("Hello, world!")
Button {
//Code here before changing the bool value
bool = true
} label: {
Text("Navigate Button")
}
}.navigationDestination(isPresented: $bool) {
SwiftUIView()
}
}
}
In this code we change take bool value as false and change it to true when our work is done using button.
.navigationDestination(isPresented: Binding<Bool>, destination: () -> View)
In .navigationDestination pass the Binding bool and provide the view you want to navigate.
You can use .navigationDestination multiple times.
Hope you found this useful.

Passing data between views in SwiftUI?

I'm trying to pass a few simple data (strings and URL and images) from one view to another view in my swiftui app.
This simple task proves to be a headache at the moment.
This is what I have done so far:
in my firstView I have this:
struct Book {
var title: String
var author: String
}
struct firstView: View {
#State private var showAudioPlayer = false
#Binding var tabSelection: Int
init(tabSelection: Binding<Int>) {
_tabSelection = tabSelection
}
var body: some View {
NavigationView {
ZStack {
Text("Tap Here")
.onTapGesture {
self.showAudioPlayer.toggle()
}
}
}
.fullScreenCover(isPresented: $showAudioPlayer, content: secondView.init)
}
}
And this is what I have in my secondView:
struct secondView: View {
#Environment(\.presentationMode) var presentationMode
var book: Book
var body: some View {
NavigationView {
ZStack{
VStack {
}
}
.background(Color.clear)
.navigationBarTitle("", displayMode: .inline)
.navigationBarItems(
leading: Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.down.circle")
.foregroundColor(Color.black)
})
.frame(maxWidth: .infinity, maxHeight: .infinity)
.edgesIgnoringSafeArea(.all)
}
.edgesIgnoringSafeArea(.all)
}
}
However, when I try to compile my code, I get this error:
The error itself doesn't make much sense and I'm stuck.
what am I doing wrong?
Memberwise Initializers for Structure Types
The compiler takes a peak into the struct and sees two non-optional properties and thinks they should be in the constructor. It doesn't know the #Environment is going to magically be set by the framework, so it includes that property. As #Baglan said above, you are on the hook for the Book and need to pass it in.
The error message is saying "you asked me to instance audioPlayerView and I need a couple of parameters to make that happen", but you really only need one.

How to make a reusable TextPopUp in SwiftUI?

I am having trouble building a reusable SwiftUI text pop up view. I think the problem is the binding but I not sure. It is supposed to be a ContextMenu, but the reason I am not using ContextMenu is it does not show enough lines of text.
So far I have this...
struct TextPopUpView: View {
#EnvironmentObject var oracleViewModel: OracleViewModel
#Binding var showPopover: Bool
var displayedText: String
var popUpText: String
var body: some View {
Text("\(displayedText)")
.font(.title)
.fontWeight(.bold)
.onLongPressGesture {
self.showPopover = true
}
.popover(isPresented: $showPopover) {
Text("\(self.popUpText)")
.frame(width: 250.0)
.onTapGesture {
self.showPopover = false
}
}
}
}
And than I implement it for a particular view like so...
TextPopUpView(showPopover: $showPopover, displayedText: oracleViewModel.***someElement***, popUpText: oracleViewModel.getDescriptionFor***SomeElement***()).environmentObject(oracleViewModel)
where 'someElement' is the particular element text and popup text I want to show.
The problem is when I use TextPopUpView more than once in a view, the Popup view only displays the text for the last implementation on the said page.
I am guessing I am doing something wrong in the implementations of a reusable view, but I am not sure what. Any suggestions?
EDIT: (Example Used in code)
struct TopView: View {
#EnvironmentObject var oracleViewModel: OracleViewModel
#State private var showPopover: Bool = false
***Blarg Blarg Blarg***
VStack{
VStack {
Text("RUNE")
.font(.system(size:10))
.padding(.bottom, 5.0)
TextPopUpView(showPopover: $showPopover,
displayedText: oracleViewModel.rune,
popUpText: oracleViewModel.getDescriptionForRune()).environmentObject(oracleViewModel)
}
.padding(.bottom)
VStack {
Text("ELEMENT")
.font(.system(size:10))
.padding(.bottom, 5.0)
TextPopUpView(showPopover: $showPopover,
displayedText: oracleViewModel.element,
popUpText: oracleViewModel.getDescriptionForElement())
.environmentObject(oracleViewModel)
}
}
***Blarg Blarg Blarg***
}
(For some reason I only get the description for the Element, when I long press either view the TextPopUp is attached to)

How to track whenever a component view is updated?

I want to track when a given view is reloaded. How do I do that?
I was thinking of printing something, but I cannot put print() in the body of a SwiftUI view
Here it is
struct Demo: View {
var body: some View {
print("<< updated")
return Group {
// ... your views here
}
}
}
if you have at top some container view you can return it directly, for example
var body: some View {
print("<< updated")
return VStack {
Text("Some 1")
Text("Some 2")
Text("Some 3")
}
}
the common rule - it must be returned single view

Is it possible for a NavigationLink to perform an action in addition to navigating to another view?

I'm trying to create a button that not only navigates to another view, but also run a function at the same time. I tried embedding both a NavigationLink and a Button into a Stack, but I'm only able to click on the Button.
ZStack {
NavigationLink(destination: TradeView(trade: trade)) {
TradeButton()
}
Button(action: {
print("Hello world!") //this is the only thing that runs
}) {
TradeButton()
}
}
You can use .simultaneousGesture to do that. The NavigationLink will navigate and at the same time perform an action exactly like you want:
NavigationLink(destination: TradeView(trade: trade)) {
Text("Trade View Link")
}.simultaneousGesture(TapGesture().onEnded{
print("Hello world!")
})
You can use NavigationLink(destination:isActive:label:). Use the setter on the binding to know when the link is tapped. I've noticed that the NavigationLink could be tapped outside of the content area, and this approach captures those taps as well.
struct Sidebar: View {
#State var isTapped = false
var body: some View {
NavigationLink(destination: ViewToPresent(),
isActive: Binding<Bool>(get: { isTapped },
set: { isTapped = $0; print("Tapped") }),
label: { Text("Link") })
}
}
struct ViewToPresent: View {
var body: some View {
print("View Presented")
return Text("View Presented")
}
}
The only thing I notice is that setter fires three times, one of which is after it's presented. Here's the output:
Tapped
Tapped
View Presented
Tapped
NavigationLink + isActive + onChange(of:)
// part 1
#State private var isPushed = false
// part 2
NavigationLink(destination: EmptyView(), isActive: $isPushed, label: {
Text("")
})
// part 3
.onChange(of: isPushed) { (newValue) in
if newValue {
// do what you want
}
}
This works for me atm:
#State private var isActive = false
NavigationLink(destination: MyView(), isActive: $isActive) {
Button {
// run your code
// then set
isActive = true
} label: {
Text("My Link")
}
}
Use NavigationLink(_:destination:tag:selection:) initializer and pass your model's property as a selection parameter. Because it is a two-way binding, you can define didset observer for this property, and call your function there.
struct ContentView: View {
#EnvironmentObject var navigationModel: NavigationModel
var body: some View {
NavigationView {
List(0 ..< 10, id: \.self) { row in
NavigationLink(destination: DetailView(id: row),
tag: row,
selection: self.$navigationModel.linkSelection) {
Text("Link \(row)")
}
}
}
}
}
struct DetailView: View {
var id: Int;
var body: some View {
Text("DetailView\(id)")
}
}
class NavigationModel: ObservableObject {
#Published var linkSelection: Int? = nil {
didSet {
if let linkSelection = linkSelection {
// action
print("selected: \(String(describing: linkSelection))")
}
}
}
}
It this example you need to pass in your model to ContentView as an environment object:
ContentView().environmentObject(NavigationModel())
in the SceneDelegate and SwiftUI Previews.
The model conforms to ObservableObject protocol and the property must have a #Published attribute.
(it works within a List)
I also just used:
NavigationLink(destination: View()....) {
Text("Demo")
}.task { do your stuff here }
iOS 15.3 deployment target.