How do I dismiss this custom modal using bindings? - binding

I cannot figure out how to dismiss a view that is showing inside its parents by way of a #State var showingCardModal = false. Here is the code for my ContentView:
import SwiftUI
struct ContentView: View {
#State var showingCardModal = false
var body: some View {
ZStack {
Button(action: {
withAnimation {
self.showingCardModal.toggle()
}
}) {
Text("Show").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.secondary).foregroundColor(.white)
.cornerRadius(12)
if showingCardModal {
CardModal()
.transition(AnyTransition.scale.combined(with: .opacity).animation(.easeIn(duration: 0.75)))
}
}
}
}
And for the CardModal inside of it:
import SwiftUI
struct CardModal: View {
//#Binding var isPresented: Bool
var body: some View {
ZStack{
Color(.secondarySystemBackground).edgesIgnoringSafeArea(.all)
VStack{
Spacer().frame(height:30)
Text("Today, 20 March").font(.title)
Spacer()
}
CarouselView(itemHeight: 420, views: [
SingleCard(name: "Card 1", contentOpacity: 1.0),
SingleCard(name: "Card 2", contentOpacity: 1.0),
SingleCard(name: "Card 3", contentOpacity: 1.0),
SingleCard(name: "Card 4", contentOpacity: 1.0),
SingleCard(name: "Card 5", contentOpacity: 1.0),
SingleCard(name: "Card 6", contentOpacity: 1.0),
SingleCard(name: "Card 7", contentOpacity: 1.0),
])
VStack {
Spacer()
Button(action:{}) {
Text("Done").font(.headline).foregroundColor(.purple)
}
.frame(width: 300, height: 48)
.background(Color.gray.opacity(0.25))
.cornerRadius(12)
Spacer().frame(height: 20)
}
}
}
}
I'm trying to replicate somewthing like in the Health App when a modal slides in from the bottom for the symptoms of a menstral cycle. THe modal is full screen and is dismissed with a button.

It can be like this...
struct CardModal: View {
#Binding var isPresented: Bool
...
Button(action:{ self.isPresented = false }) {
and in ContentView
if showingCardModal {
CardModal(isPresented: self.$showingCardModal)
and in PreviewProvider
CardModal(isPresented: .constant(true))

Related

SwiftUi- Cannot convert value of type Binding<[ExercisePlan]>.Element to expected argument type Binding<[ExercisePlan]>

I'm trying to create a NavigationLink to another view (ExercisePlanDetailView) and it requires me to pass the #State var exercisePlan as a binding var but it keeps giving me this error. I have a #Binding var inside the ExercisePlan Detail view too.
ExercisePlanView:
`
import SwiftUI
struct ExercisePlanView: View {
#State var isSheetPresented = false
#State var exercisePlan = [
ExercisePlan(title: "Exercise Plan 1", details: "Choose this for a light and basic workout", exercise:
Exercise(title: "Toe touches and Arm Stretches", duration: 5), exercise2: Exercise(title: "ArmCircles", duration: 5), exercise3: Exercise(title: "Calf Raises", duration: 5), exercise4: Exercise(title: "Jog on the Spot", duration: 5)
),
ExercisePlan(title: "Exercise Plan 2", details: "Choose this for a medium difficulty workout", exercise: Exercise(title: "High Knee March", duration: 5), exercise2: Exercise(title: "Stair Exercise", duration: 10), exercise3: Exercise(title: "Chair Exercise", duration: 5), exercise4: Exercise(title: "Lunges", duration: 5)),
// ExercisePlan(title: "Exercise Plan 2", details: "Choose this plan for a more intermediate set of workouts", exercise: Exercise(title: "Wall Push-ups", duration: <#T##Int#>), exercise2: <#T##Exercise#>, exercise3: <#T##Exercise#>, exercise4: <#T##Exercise#>)
]
var body: some View {
let row = GridItem(.fixed(50), spacing: 20, alignment: .center)
ScrollView(.horizontal) {
LazyHGrid(rows: [row]) {
ForEach($exercisePlan) { $exercisePlan in
NavigationLink {
ExercisePlanDetailView(exercisePlan: $exercisePlan)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} label: {
Text(exercisePlan.title)
.foregroundColor(.black)
.frame(width: 120, height: 130)
.padding()
.background((Color(red: 220/255, green: 247/255, blue: 99/255)))
.cornerRadius(10)
}
}
// .sheet(isPresented: $isSheetPresented) {
// ExercisePlanDetailView(exercisePlan: $exercisePlan)
}
}
}
}
struct ExercisePlanView_Previews: PreviewProvider {
static var previews: some View {
ExercisePlanView()
}
}
`
ExercisePlanDetailView:
import SwiftUI
struct ExercisePlanDetailView: View {
#Binding var exercisePlan: [ExercisePlan]
var body: some View {
VStack {
Text($exercisePlan.title)
}
}
}
struct ExercisePlanDetailView_Previews: PreviewProvider {
static var previews: some View {
ExercisePlanDetailView(exercisePlan: .constant([]))
}
}
Try this approach, where you pass only the desired exercisePlan
to the details view, not the whole array:
In ExercisePlanView
ForEach($exercisePlan) { $plan in
//..
ExercisePlanDetailView(exercisePlan: $plan)
//...
In ExercisePlanDetailView
#Binding var exercisePlan: ExercisePlan
//...
Text(exercisePlan.title)
//...
And in the ExercisePlanDetailView_Previews
ExercisePlanDetailView(exercisePlan:.constant(ExercisePlan(title:....)))

Animated Expand/Collapse group in horizontal ScrollView (SwiftUI)

I have a simple ForEach showing 5 or 10 Text views according to the expanded flag. everything is wrapped in a horizontal ScrollView because the text can be very long.
The animation when expanding the group looks fine, but when collapsing the group there is a small bouncing, the views goes up and down. This does not happen if I remove the ScrollView. Any idea what could cause this bouncing?
struct ContentView: View {
#State var expanded = false
let colors: [Color] = [.red, .green, .blue, .orange, .blue, .brown, .cyan, .gray, .indigo, .mint]
var body: some View {
VStack {
ScrollView(.horizontal) {
VStack(spacing: 20) {
ForEach(colors.prefix(upTo: expanded ? 10 : 5), id: \.self) { color in
Text(color.description.capitalized)
}
}
}
Button("Expand/collapse") {
expanded.toggle()
}
Spacer()
}.animation(.easeIn(duration: 1))
}
}
You need to use value for animation:
struct ContentView: View {
#State var expanded = false
let colors: [Color] = [.red, .green, .blue, .orange, .blue, .red, .green, .blue, .orange, .blue]
var body: some View {
VStack {
ScrollView(.horizontal) {
VStack(spacing: 20) {
ForEach(colors.prefix(upTo: expanded ? 10 : 5), id: \.self) { color in
Text(color.description.capitalized)
}
}
}
Button("Expand/collapse") {
expanded.toggle()
}
Spacer()
}.animation(.easeIn(duration: 1), value: expanded) // <<: Here!
}
}

SwiftUI Picker manually trigger expansion

I've got the following view:
The Swift code looks like this:
struct TestView: View {
let options = [" ", "1", "2", "3", "4", "5", "6"]
#State var selectedIndex: Int = 0
var body: some View {
HStack(spacing: 0) {
Text("One")
Spacer()
Picker(selection: $selectedIndex, label: Text(options[selectedIndex])) {
ForEach(0 ..< options.count) {
Text(options[$0])
}
}
.background(Color.red)
.pickerStyle(MenuPickerStyle())
}
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.background(Color.yellow)
}
}
When clicking on the red square, the Picker will be opened:
How can I extend the touch area of the red rectangle to also include the entire yellow area?
#DonMag's answer stopped working with iOS 15. Here's an updated answer that does work. Technically, it does not use Slider, the behavior is the same though. Instead a Menu is used.
struct PickerTestView: View {
let options = [" ", "1", "2", "3", "4", "5", "6"]
let optionNames = [" ", "One", "Two", "Three", "Four", "Five", "Six"]
#State var selectedIndex: Int = 0
var body: some View {
ZStack {
HStack(spacing: 0) {
Text(optionNames[selectedIndex])
Spacer()
}
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.background(Color.yellow)
HStack(spacing: 0) {
Menu {
ForEach(0 ..< options.count) {
let index = $0
Button("\(options[index])") {
selectedIndex = index
}
}
} label: {
Label("", image: "")
.labelStyle(TitleOnlyLabelStyle())
.frame(maxWidth: .infinity)
}
}
}
}
}
struct PickerTestView_Previews: PreviewProvider {
static var previews: some View {
PickerTestView()
}
}
Let's see when Apple decides to break this implementation.
Not sure this is exactly what you're after, but give it a try (initial view is a "blank" yellow bar):
import SwiftUI
struct PickerTestView: View {
let options = [" ", "1", "2", "3", "4", "5", "6"]
let optionNames = [" ", "One", "Two", "Three", "Four", "Five", "Six"]
#State var selectedIndex: Int = 0
var body: some View {
ZStack {
HStack(spacing: 0) {
Text(optionNames[selectedIndex])
Spacer()
}
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
.background(Color.yellow)
HStack(spacing: 0) {
Picker(selection: $selectedIndex, label: Text(" ").frame(maxWidth: .infinity), content: {
ForEach(0 ..< options.count) {
Text(options[$0])
}
})
.pickerStyle(MenuPickerStyle())
}
}
}
}
struct PickerTestView_Previews: PreviewProvider {
static var previews: some View {
PickerTestView()
}
}
On launch:
Tap anywhere on the yellow bar:
After selecting "3":

SwiftUI WidgetKit: How to anchor views to the top

I am attempting to layout a tableView using SwiftUI and WidgetKit and would like to achieve a similar result of that as the Apple's Notes widget.
My current implementation succeeds in laying out the view in the .systemLarge widget, but not in the .systemMedium widget. I would like to pin the view to the top of the widget, such that the header of "FAVOURITES" is visible in the .systemMedium.
struct PlacesWidgetEntryView : View {
var entry: Provider.Entry
let places = [
Place(name: "Place 1", imageName: "baseline_star_black_24pt"),
Place(name: "Place 2", imageName: "baseline_star_black_24pt"),
Place(name: "Place 3", imageName: "baseline_star_black_24pt"),
Place(name: "Place 4", imageName: "baseline_star_black_24pt"),
Place(name: "Place 5", imageName: "baseline_star_black_24pt"),
]
var body: some View {
VStack {
//Header
HStack {
Text("FAVOURITES")
.bold()
.frame(height: 8)
Spacer()
}
.padding()
.background(Color.blue)
//TableView
LazyVStack {
ForEach(places, id: \.self) { place in
PlaceRow(place: place)
}
}
Spacer()
}
}
}
struct PlaceRow: View {
let place: Place
var body: some View {
HStack {
Text(place.name)
.font(.body)
Spacer()
Image(place.imageName)
.resizable()
.frame(width: 28, height: 28, alignment: .center)
}
.padding(.horizontal)
.padding(.vertical, 4)
}
}
Implementation outcome:
The above is .systemLarge, which is good, and as per what I'm expecting.
The above is .systemMedium, which is not what I'm expecting. I would like to see "Favourites" anchored to the top of the widgetView, and potentially the tableView overflowing to the bottom.
Here is possible layout solution. Tested with Xcode 12.
var body: some View {
VStack(spacing: 0) {
HStack {
Text("FAVOURITES")
.bold()
.frame(height: 8)
Spacer()
}
.padding()
.background(Color.blue)
Color.clear
.overlay(
LazyVStack {
ForEach(places, id: \.self) { place in
PlaceRow(place: place)
}
},
alignment: .top)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
So from playing around in WidgetKit, it seems like if there are too many views in a widget, it starts to push the upper views off the screen. If you add more places to the array, you'll see the same thing happen with your large widget. What you can do is create separate views: one for the medium and one for the large widget, and for the medium one, just use 1-3 of your place objects to populate it.
You can use a switch statement in your PlacesEntryWidgetView with the widgetFamily to decide what you want to show on the view. I also slightly reduced the height of the image from 28 to 24.
struct PlacesWidgetEntryView : View {
var entry: Provider.Entry
#Environment(\.widgetFamily) var family
let places = [
Place(name: "Place 1", imageName: "blackStar"),
Place(name: "Place 2", imageName: "blackStar"),
Place(name: "Place 3", imageName: "blackStar"),
Place(name: "Place 4", imageName: "blackStar"),
Place(name: "Place 5", imageName: "blackStar"),
Place(name: "Place 6", imageName: "blackStar"),
Place(name: "Place 7", imageName: "blackStar"),
Place(name: "Place 8", imageName: "blackStar")
]
#ViewBuilder
var body: some View {
switch family {
case .systemMedium:
// widget can only show so many views so I only took first 3 places
WidgetView(places: Array(places.prefix(3)))
case .systemLarge:
WidgetView(places: places)
// I only have it set to show system medium so you can ignore
// the last case
case .systemSmall:
Text("")
#unknown default:
Text("")
}
}
}
struct WidgetView: View {
let places: [Place]
var body: some View {
VStack {
//Header
HStack {
Text("FAVOURITES")
.bold()
.frame(height: 8)
Spacer()
}
.padding()
.background(Color.blue)
//TableView
LazyVStack {
ForEach(places, id: \.self) { place in
PlaceRow(place: place)
}
}
Spacer()
}
}
}
struct PlaceRow: View {
let place: Place
var body: some View {
HStack {
Text(place.name)
.font(.body)
Spacer()
Image(place.imageName)
.resizable()
.frame(width: 28, height: 24, alignment: .center)
}
.padding(.horizontal)
.padding(.vertical, 4)
}
}
This is the preview:
Keep in mind, that you might want to switch between the simulators to make sure your widgets look good on all devices.

Change the background color of a view in swiftUI

I want to make a simple form and change the background color of a text field, but Xcode provide me .background(background: View), etc option but not .background(Color()).
Color is conformed to View. So you can use it like any other View. The issue with your code is you add cornerRadius inside the background modifier. It should be out like this:
TextField("Title", text: .constant("text"))
.background(Color.red)
.cornerRadius(5)
Try below code.
struct ContentView: View {
#State var name: String = ""
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("NAME").font(.headline)
TextField("Enter some text", text: $name)
.padding(.horizontal , 15)
.frame(height: 40.0)
.background(Color(red: 239/255, green: 243/255, blue: 244/255))
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.gray.opacity(0.3), lineWidth: 1)
)
}.padding(.horizontal , 15)
}
}
import SwiftUI
struct ContentView: View {
#State var name: String = ""
var body: some View {
VStack {
TextField("Enter some text", text: $name)
.background(Color.red)
}
}
}

Resources