I want to use multiple popovers with buttons in the Swift UI - swift

When using Popover with a button in SwiftUI, I want to popover with multiple buttons as shown below, but as it is, only the upper button
I can't get a popover. What if you want to popover both separately?
struct MySelection: Identifiable {
let id = UUID()
var text = ""
}
struct PopoverTest: View {
#State var selected: MySelection?
var body: some View {
VStack (spacing: 88) {
// First Button
Button(action: {
selected = MySelection(text: "Popover1")
}, label: {
Image(systemName: "stopwatch")
})
// Second Button
Button(action: {
selected = MySelection(text: "Popover2")
}, label: {
Image(systemName: "globe")
})
}
.buttonStyle(.plain)
.popover(item: $selected) { selection in
Text(selection.text).font(.largeTitle)
}
}
}

Thinking it over, the better way might be to create a custom "button with popover" view that can be used anywhere:
struct ContentView: View {
var body: some View {
VStack (spacing: 88) {
// First Button
ButtonWithPopover(image: "stopwatch",
item: MySelection(text: "Popover1"))
// Second Button
ButtonWithPopover(image: "globe",
item: MySelection(text: "Popover2"))
}
.buttonStyle(.plain)
}
}
struct ButtonWithPopover: View {
let image: String
let item: MySelection
#State var selected: MySelection?
var body: some View {
Button(action: {
selected = item
}, label: {
Image(systemName: image)
})
.popover(item: $selected) { selection in
Text(selection.text).font(.largeTitle)
}
}
}

In this case it seems you have to introduce 2 selected vars. The optional selected can hand over different values to the popover, but if it is triggered from different subviews, SwiftUI doesn't know where to anchor the popover to.
struct ContentView: View {
#State var selected1: MySelection?
#State var selected2: MySelection?
var body: some View {
VStack (spacing: 88) {
// First Button
Button(action: {
selected1 = MySelection(text: "Popover1")
}, label: {
Image(systemName: "stopwatch")
})
.popover(item: $selected1) { selection in
Text(selection.text).font(.largeTitle)
}
// Second Button
Button(action: {
selected2 = MySelection(text: "Popover2")
}, label: {
Image(systemName: "globe")
})
.popover(item: $selected2) { selection in
Text(selection.text).font(.largeTitle)
}
}
.buttonStyle(.plain)
}
}

Related

Binding a button leads to "Missing argument for parameter in call" error

I'm trying to create a Binding in two views so I can change something on one side and have it reflected on the other.
I basically have:
a circle on both views
a button to change the other view's circle color
and one to go to the other view
It all works fine if I only have a Binding in the "ColorChange2"
view, but when I add a Binding in "ColorChange1" I get into trouble.
It tells me: Missing argument for parameter 'isOn2'.
But when I add isOn2 into ColorChange1() it wants a binding, but if I do ColorChange1(isOn2: $isOn2) it says it can't find '$isOn2' in scope.
I found one solution suggesting to add .constant(true)) into the preview but since it's a constant, it wont change the view like I wanted since it's a constant.
What can I do to make it work?
Code:
struct ColorChange1: View {
#State private var isOn = false
#Binding var isOn2 : Bool
var body: some View {
NavigationView {
VStack {
Circle()
.fill(isOn ? .green : .red)
.frame(width: 100)
Button(action: {
isOn2.toggle()
}, label: {
Text("Change button view 2")
.padding()
})
NavigationLink(destination: {
ColorChange2(isOn: $isOn)
}, label: {
Text("Go to view 2")
})
}
}
}
}
struct ColorChange2: View {
#Binding var isOn : Bool
#State private var isOn2 = false
#Environment(\.dismiss) var dismiss
var body: some View {
VStack {
Circle()
.fill(isOn2 ? .green : .red)
.frame(width: 100)
Button(action: {
isOn.toggle()
}, label: {
Text("Change button view 1")
.padding()
})
Button(action: {
dismiss.callAsFunction()
}, label: {
Text("Go to view 1")
})
}
.navigationBarBackButtonHidden(true)
}
}
struct ColorChange_Previews: PreviewProvider {
static var previews: some View {
// ColorChange(isOn2: .constant(true))
ColorChange1()
}
} ```
You don't need both #Binding value in both screen to connect between screen like that.
#Binding means that get the value in #State of the first view and make a connection in the second view. In this scenero, when you go back from second view, it was dismissed.
For your problem, make an ObservableObject to store value. 1 for present in first view and 1 for second view. Then add it to second view when ever you need to display.
Code will be like this
class ColorModel : ObservableObject {
#Published var isOnFirstView = false
#Published var isOnSecondView = false
func didTapChangeColor(atFirstView: Bool) {
if atFirstView {
isOnSecondView = !isOnSecondView
} else {
isOnFirstView = !isOnFirstView
}
}
}
struct ColorChange2: View {
// binding model
#ObservedObject var colorModel : ColorModel
#Environment(\.dismiss) var dismiss
var body: some View {
VStack {
Circle()
.fill(colorModel.isOnSecondView ? .green : .red)
.frame(width: 100)
Button(action: {
colorModel.didTapChangeColor(atFirstView: false)
}, label: {
Text("Change button view 1")
.padding()
})
Button(action: {
dismiss.callAsFunction()
}, label: {
Text("Go to view 1")
})
}
.navigationBarBackButtonHidden(true)
}
}
struct ColorChange1: View {
#StateObject private var colorModel = ColorModel()
var body: some View {
NavigationView {
VStack {
Circle()
.fill(colorModel.isOnFirstView ? .green : .red)
.frame(width: 100)
Button(action: {
colorModel.didTapChangeColor(atFirstView: true)
}, label: {
Text("Change button view 2")
.padding()
})
NavigationLink(destination: {
ColorChange2(colorModel: colorModel)
}, label: {
Text("Go to view 2")
})
}
}
}
}
struct ColorChange_Previews: PreviewProvider {
static var previews: some View {
ColorChange1()
}
}

SwiftUI - ForEach Simultaneously Toggle Multiple Buttons

I have a ForEach loop that displays ten unfilled buttons. When the button is clicked, the button assigns the index of the clicked button to a #State variable; only when the value of the state variable is equal to the index of the clicked button is the button filled. What should I change to be able to 1) click on the same button to unfill the button, and 2) fill another button without "unfilling" any already filled buttons?
Here is a minimal, reproducible example:
import SwiftUI
struct Test: View {
#State var selection: Int? = nil
var body: some View {
VStack {
ForEach (0 ..< 10, id: \.self) { number in
Button (action: {
self.selection = number
}, label: {
self.selection == number ? Image(systemName: "heart.fill") : Image(systemName: "heart")
})
}
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
Thanks in advance.
You need to have a selection property that can keep track of multiple buttons, not just one button. I would use a Set here (not an Array, because there's no handy remove-object method there)
struct Test: View {
#State var selection = Set<Int>() /// will contain the indices of all selected buttons
var body: some View {
VStack {
ForEach (0 ..< 10, id: \.self) { number in
Button(action: {
if selection.contains(number) {
selection.remove(number)
} else {
selection.insert(number)
}
}) {
selection.contains(number) ? Image(systemName: "heart.fill") : Image(systemName: "heart")
}
}
}
}
}
You have to maintain state separately for each button.Better option is to use array. Check code below.
import SwiftUI
struct Test: View {
#State var selection: [Int] = Array(repeating:-1,count:10)
var body: some View {
VStack {
ForEach (0..<selection.count, id: \.self) { index in
Button (action: {
if self.selection[index] == index {
self.selection[index] = -1
}else{
self.selection[index] = index
}
}, label: {
self.selection[index] == index ? Image(systemName: "heart.fill") : Image(systemName: "heart")
})
}
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}

How to navigate with a custom button without changing styling?

If I don't use .disabled(true) then the button does not navigate. If I used .disabled(true) then the style changes to make the foreground a grayed version of the foreground color. I want to navigate without changing the style of my custom button. I used a non-custom button in the example to save space.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: ContentView2(),
label: {
Button(action: {}, label: {
Text("Button")
}).disabled(true)
})
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct ContentView2: View {
var body: some View {
Text("wdfokjokjokjokjwdofjk")
.padding()
}
}
Instead of making your own Button, just use Text. When you are doing .disabled(true) to make a button active, something is definitely wrong...
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: ContentView2()) {
Text("Button")
}
}
}
}
You can use an isActive parameter for the NavigationLink and set it as an invisible background or overlay:
struct ContentView: View {
#State private var navLinkActive = false
var body: some View {
NavigationView {
Button(action: {
navLinkActive = true
}, label: {
Text("Button")
})
.buttonStyle(CustomButtonStyle())
.background(NavigationLink(
destination: ContentView2(),
isActive: $navLinkActive,
label: {
EmptyView()
}))
}
}
}
struct CustomButtonStyle : ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.background(configuration.isPressed ? Color.red : Color.green)
}
}

SwiftUI picker with a button in the navbar

On the track to learn more and more about SwiftUI. I come accross some weird behaviors.
I have a simple view called Modal. I am using a Picker in it and set a title in the navigation bar to go in the detail view.
That works fine. The problem starts when I add a button in the nav bar.
It end up looking like this
Without the + button
With the + button
And the code is the following:
ContentView.swift
import SwiftUI
struct ContentView: View {
#State var isShowing = false
var body: some View {
VStack(content: {
Button("Modal") {
isShowing = true
}
})
.sheet(isPresented: $isShowing, content: content)
}
#ViewBuilder
func content() -> some View {
Modal()
}
}
Modal.swift
import SwiftUI
import Combine
struct Modal: View {
#State var selection: String = ""
#State var list: [String] = ["1", "2", "3", "4", "5", "6"]
var body: some View {
NavigationView(content: {
Form(content: {
self.type()
})
.navigationBarTitle("Modal", displayMode: .inline)
})
}
}
private extension Modal {
func type() -> some View {
Section(content: {
Picker(selection: $selection, label: Text("Type").bold()) {
ForEach(list, id: \.self) { item in
Text(item)
.tag(UUID())
}
.navigationBarTitle("Select")
.navigationBarItems(trailing: button())
}
})
}
func button() -> some View {
HStack(alignment: .center, content: {
Button(action: {
// Action
}) {
Image(systemName: "plus")
}
})
}
}
This is because .navigationBarItems modifier generates flat view from dynamic ForEach views, attach instead it to one view inside ForEach, like
Section(content: {
Picker(selection: $selection, label: Text("Type").bold()) {
ForEach(list, id: \.self) { item in
if item == list.last {
Text(item)
.navigationBarTitle("Select")
.navigationBarItems(trailing: button())
.tag(UUID())
} else {
Text(item)
.tag(UUID())
}
}
}
})

SwiftUI: How can I change the Color of Alert Button and NavigationLink back button?

How can I change the Color from the Button in a Alert and the back Button from NavigationLink? To set .accentColor(.init("someColor")) after the Text from the Alert Button doesn't work. To set .accentColor(.init("someColor")) after navigationLink does not work too. What can I do?
The Alert Button:
The Back Button from NavigationLink:
struct ContentView: View {
#State var showSheet = false
#State var alertShouldBeShown = !UserDefaults.standard.bool(forKey: "£Start")
var body: some View {
Button(action: {
self.showSheet.toggle()
}) {
Text("click me")
//.accentColor(colorScheme == .dark ? Image("") : Image("Info-Icon"))
}.sheet(isPresented: $showSheet) {
SheetView(showSheet: self.$showSheet)
}
.alert(isPresented: $alertShouldBeShown, content: {
Alert(title: Text("Headline"),
message: Text("Text"),
dismissButton: Alert.Button.default(
Text("Ok"), action: {
UserDefaults.standard.set(true, forKey: "Start")
}
)
)
})
}
}
struct SheetView: View {
#Binding var showSheet: Bool
var body: some View {
NavigationView {
DetailView()
.navigationBarTitle(Text("Headline"), displayMode: .inline)
.navigationBarItems(trailing: Button(action: {
self.showSheet = false
}) {
Text("Done")
.bold()
})
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailView: View {
var body: some View {
List {
HStack {
NavigationLink(destination: DetailViewOne()) {
Text("View 1")
}
}
HStack {
NavigationLink(destination: DetailViewTwo()) {
Text("View 2")
}
}
}
}
}
struct DetailViewOne: View {
var body: some View {
Text("1")
}
}
struct DetailViewTwo: View {
var body: some View {
Text("2")
}
}
You can change the NavigationBar accent color by using accentColor but you need to apply it to the SheetView (the root view of a given environment):
SheetView(showSheet: self.$showSheet)
.accentColor(.red)
Unfortunately SwiftUI doesn't allow much of Alert customisation so far.
However, as Alert is built on top of UIKit components (UIAlertController) this also means you can change the appearance of UIView when contained in UIAlertController.
You can put this code in your #main App init or in the SceneDelegate:
UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .red