SwiftUI Segmented Picker does not switch when click on it - swift

I'm trying to implement Segmented Control with SwiftUI. For some reason Segmented Picker does not switch between values when click on it. I went through many tutorials but cannot find any difference from my code:
struct MeetingLogin: View {
#State private var selectorIndex: Int = 0
init() {
UISegmentedControl.appearance().backgroundColor = UIColor(named: "JobsLightGreen")
UISegmentedControl.appearance().selectedSegmentTintColor = UIColor(named: "AlmostBlack")
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white,
.font: UIFont(name: "OpenSans-Bold", size: 13)!],
for: .selected)
UISegmentedControl.appearance().setTitleTextAttributes([.foregroundColor: UIColor.white,
.font: UIFont(name: "OpenSans-Regular", size: 13)!],
for: .normal)
}
var body: some View {
VStack {
...
Group {
Spacer().frame(minHeight: 8, maxHeight: 30)
Picker("video", selection: $selectorIndex) {
Text("Video On").tag(0)
Text("Video Off").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding([.leading, .trailing], 16)
Spacer().frame(minHeight: 8, maxHeight: 50)
Button(action: { self.sendPressed() }) {
ZStack {
RoundedRectangle(cornerRadius: 100)
Text("go")
.font(.custom("Roboto-Bold", size: 36))
.foregroundColor(Color("MeetingGreen"))
}
}
.foregroundColor(Color("MeetingLightGreen").opacity(0.45))
.frame(width: 137, height: 73)
}
Spacer()
}
}
}
Would appreciate any suggestions!

Update: The issue seem to have occured due to overlapping of the View's because the cornerRadius value of RoundedRectangle was set to 100. Bringing the value of cornerRadius to 55 would restore the Picker's functionality.
The issue seems to be caused because of the RoundedRectangle(cornerRadius: 100) within the ZStack. I don't have and explanation as to why this is happening. I'll add the reason if I ever find out. Could be a SwiftUI bug. I can't tell untill I find any related evidence. So here is the code that could make the SegmentedControl work without any issue.
struct MeetingLogin: View {
//...
#State private var selectorIndex: Int = 0
var body: some View {
VStack {
//...
Group {
Spacer().frame(minHeight: 8, maxHeight: 30)
Picker("video", selection: $selectorIndex) {
Text("Video On").tag(0)
Text("Video Off").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
.padding([.leading, .trailing], 16)
Spacer().frame(minHeight: 8, maxHeight: 50)
Button(action: { self.sendPressed() }) {
ZStack {
RoundedRectangle(cornerRadius: 55)
Text("go")
.font(.custom("Roboto-Bold", size: 36))
.foregroundColor(Color("MeetingGreen"))
}
}
.foregroundColor(Color("MeetingLightGreen").opacity(0.45))
.frame(width: 137, height: 73)
}
Spacer()
}
}
}

Xcode 12 iOS 14
you can get it by adding a if-else condition on the tapGesture of your Segment Control(Picker)
#State private var profileSegmentIndex = 0
Picker(selection: self.$profileSegmentIndex, label: Text("Jamaica")) {
Text("My Posts").tag(0)
Text("Favorites").tag(1)
}
.onTapGesture {
if self.profileSegmentIndex == 0 {
self.profileSegmentIndex = 1
} else {
self.profileSegmentIndex = 0
}
}
.pickerStyle(SegmentedPickerStyle())
.padding()
If you need to use it with more than 2 segments, you can try with an Enum :)

Your code/View is missing the if-condition to know what shall happen when the selectorIndex is 0 or 1.
It has to look like this:
var body: some View {
VStack {
if selectorIndex == 0 {
//....Your VideoOn-View
} else {
//Your VideoOff-View
}
}

Related

SwiftUI Animation causes binding conditional view to flash on and off

I have an issue with this setup:
ZStack {
ViewOne
if something ? ViewTwo : nil
}
.animation()
The problem is that when the animation starts, ViewTwo flashes on and off. I'm thinking it has something to do with the view re-rendering or something? But I can't quite figure it out. I've tried moving the animation around, using it on each view separately, combining it all in one view, but it always flashes when it's based on a conditional. I'd like BOTH views to animate together.
Here is a piece of reproducible code snippet.
struct ContentView: View {
#State var isAnimating: Bool
var body: some View {
ZStack {
VStack {
ForEach((1...5).reversed(), id: \.self) {_ in
ZStack {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.blue)
.frame(width: 200, height: 50)
.rotationEffect(.degrees(isAnimating == true ? 5 : 0))
isAnimating
? ButtonImage()
: nil
}
.animation(
.easeInOut(duration: 0.3)
.repeatForever(autoreverses: true)
, value: isAnimating
)
}
Button(action: {
self.isAnimating.toggle()
}, label: {
Text("Animate")
})
}
}
.rotationEffect(.degrees(isAnimating == true ? 5 : 0))
}
}
struct ButtonImage: View {
private let buttonSize: CGSize = CGSize(width: 25, height: 25)
var body: some View {
Button(action: {
// to something
}) {
Image(systemName: "flame")
.resizable()
.renderingMode(.template)
.background(Color.red)
.foregroundColor(Color.yellow)
.frame(width: buttonSize.width, height: buttonSize.height)
}
.frame(width: buttonSize.width, height: buttonSize.height)
.offset(x: -buttonSize.width / 2, y: -buttonSize.height / 2)
}
Any ideas of how to resolve this? Showing a view based on a condition, while also animating it without it flashing?
Figured out a way! Not sure if it's the best approach, but it works.
Add the animation to both views separately, then add an opacity modifier to the second view. Here is the code I used.
struct ContentView: View {
#State var isAnimating: Bool
var body: some View {
ZStack {
VStack {
ForEach((1...5).reversed(), id: \.self) {_ in
ZStack {
RoundedRectangle(cornerRadius: 5)
.foregroundColor(.blue)
.frame(width: 200, height: 50)
.rotationEffect(.degrees(isAnimating == true ? 5 : 0))
.animation(
.easeInOut(duration: 0.3)
.repeatForever(autoreverses: true)
, value: isAnimating
)
ButtonImage(isAnimating: $isAnimating)
.opacity(isAnimating ? 1 : 0)
}
}
Button(action: {
self.isAnimating.toggle()
}, label: {
Text("Animate")
})
}
}
.rotationEffect(.degrees(isAnimating == true ? 5 : 0))
}
}
struct ButtonImage: View {
private let buttonSize: CGSize = CGSize(width: 25, height: 25)
#Binding var isAnimating: Bool
var body: some View {
Button(action: {
// to something
}) {
Image(systemName: "flame")
.resizable()
.renderingMode(.template)
.background(Color.red)
.foregroundColor(Color.yellow)
.frame(width: buttonSize.width, height: buttonSize.height)
}
.frame(width: buttonSize.width, height: buttonSize.height)
.offset(x: -buttonSize.width / 2, y: -buttonSize.height / 2)
.rotationEffect(.degrees(isAnimating == true ? 5 : 0))
.animation(
.easeInOut(duration: 0.3)
.repeatForever(autoreverses: true)
, value: isAnimating
)
}
}

Animating a flashing bell in SwiftUI

I am having problems with making a simple systemIcon flash in SwiftUI.
I got the animation working, but it has a silly behaviour if the layout of
a LazyGridView changes or adapts. Below is a video of its erroneous behaviour.
The flashing bell stays in place but when the layout rearranges the bell
starts transitioning in from the bottom of the parent view thats not there anymore.
Has someone got a suggestion how to get around this?
Here is a working example which is similar to my problem
import SwiftUI
struct FlashingBellLazyVGrid: View {
#State var isAnimating = false
#State var showChart = true
var body: some View {
let columns = [GridItem(.adaptive(minimum: 300), spacing: 50, alignment: .center)]
VStack {
Button(action: {
showChart.toggle()
}) {
VStack {
Circle()
.fill(showChart ? Color.green : Color.red)
.shadow(color: Color.gray, radius: 5, x: 2, y: 2)
Text("Charts")
.foregroundColor(Color.primary)
}.frame(width: 150, height: 50)
}
ScrollView {
LazyVGrid (
columns: columns, spacing: 50
) {
ForEach(0 ..< 25) { item in
ZStack {
Rectangle()
.fill(Color.red)
.cornerRadius(15)
VStack {
HStack {
Text(/*#START_MENU_TOKEN#*/"Hello, World!"/*#END_MENU_TOKEN#*/)
Spacer()
Image(systemName: "bell.fill")
.foregroundColor(Color.yellow)
.opacity(self.isAnimating ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.66).repeatForever(autoreverses: false))
.onAppear{ self.isAnimating = true }
}.padding(50)
if showChart {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
}
}
}
}
}
}
}
struct FlashingBellLazyVGrid_Previews: PreviewProvider {
static var previews: some View {
FlashingBellLazyVGrid()
}
}
how it looks like before you click the showChart button at the top
After you toggle the button it looks like the bells are erroneously moving into place from the bottom of the screen. and toggling it back to its original state doesn't resolve this bug subsequently.
[
Looks like the animation is basing itself off of the original size of the view. In order to trick it into recognizing the new view size, I used .id(UUID()) on the outside of the grid. In a real world application, you'd probably want to be careful to store this ID somewhere and only refresh it when needed -- not on every re-render like I'm doing:
struct FlashingBellLazyVGrid: View {
#State var showChart = true
let columns = [GridItem(.adaptive(minimum: 300), spacing: 50, alignment: .center)]
var body: some View {
VStack {
Button(action: {
showChart.toggle()
}) {
VStack {
Circle()
.fill(showChart ? Color.green : Color.red)
.shadow(color: Color.gray, radius: 5, x: 2, y: 2)
Text("Charts")
.foregroundColor(Color.primary)
}.frame(width: 150, height: 50)
}
ScrollView {
LazyVGrid (
columns: columns, spacing: 50
) {
ForEach(0 ..< 25) { item in
ZStack {
Rectangle()
.fill(Color.red)
.cornerRadius(15)
VStack {
SeparateComponent()
if showChart {
Rectangle()
.fill(Color.green)
.frame(height: 200)
}
}
}
}
}
.id(UUID()) //<-- Here
}
}
}
}
struct SeparateComponent : View {
#State var isAnimating : Bool = false
var body: some View {
HStack {
Text("Hello, World!")
Spacer()
Image(systemName: "bell.fill")
.foregroundColor(Color.yellow)
.opacity(self.isAnimating ? 1 : 0)
.animation(Animation.easeInOut(duration: 0.66).repeatForever(autoreverses: false))
.onAppear{
self.isAnimating = true
}
}
.padding(50)
}
}
I also separated out the blinking component into its own view, since there were already problematic things happening with the existing logic with onAppear, which wouldn't affect newly-scrolled-to items correctly. This may need refactoring for your particular case as well, but this should get you started.

Add shadow above SwiftUI's TabView

I'm trying to implement TabView in SwiftUI that has the same color as screen background but also has a shadow above it like in this picture:
So far I've been able to properly display color, but I don't know how to add the shadow. This is my code:
struct ContentView: View {
init() {
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
UITabBar.appearance().standardAppearance = appearance
}
var body: some View {
TabView {
Text("First View")
.tabItem {
Image(systemName: "square.and.arrow.down")
Text("First")
}
Text("Second View")
.tabItem {
Image(systemName: "square.and.arrow.up")
Text("Second")
}
}
}
}
Does anyone know how to do this? I would appreciate your help :)
Short answer
I found a solution. You can create your own shadow image and add it to the UITabBar appearance like this:
// load your custom shadow image
let shadowImage: UIImage = ...
//you also need to set backgroundImage, without it shadowImage is ignored
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().shadowImage = shadowImage
More detailed answer
Setting backgroundImage
Note that by setting
UITabBar.appearance().backgroundImage = UIImage()
you make your TabView transparent, so it is not ideal if you have content that can scroll below it. To overcome this, you can set TabView's color.
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor.systemGray6
UITabBar.appearance().standardAppearance = appearance
Setting shadowImage
I wanted to generate shadow image programatically. For that I've created an extension of UIImage. (code taken from here)
extension UIImage {
static func gradientImageWithBounds(bounds: CGRect, colors: [CGColor]) -> UIImage {
let gradientLayer = CAGradientLayer()
gradientLayer.frame = bounds
gradientLayer.colors = colors
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
}
And finally I styled my TabView like this:
let image = UIImage.gradientImageWithBounds(
bounds: CGRect( x: 0, y: 0, width: UIScreen.main.scale, height: 8),
colors: [
UIColor.clear.cgColor,
UIColor.black.withAlphaComponent(0.1).cgColor
]
)
let appearance = UITabBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundColor = UIColor.systemGray6
appearance.backgroundImage = UIImage()
appearance.shadowImage = image
UITabBar.appearance().standardAppearance = appearance
Result
Your best bet in order to achieve pretty much exactly what you wish is to create a custom TabView.
In fact, in SwiftUI you could use UITabBarAppearance().shadowColor, but that won't do much apart from drawing a 2px line on top of the TabView itself.
Instead, with the below code, you could create a custom TabView and achieve the desired graphical effect.
import SwiftUI
enum Tab {
case borrow,ret,device,you
}
struct TabView: View {
#Binding var tabIdx: Tab
var body: some View {
HStack {
Group {
Spacer()
Button (action: {
self.tabIdx = .borrow
}) {
VStack{
Image(systemName: "arrow.down.circle")
Text("Borrow")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .borrow ? .purple : .secondary)
Spacer()
Button (action: {
self.tabIdx = .ret
}) {
VStack{
Image(systemName: "arrow.up.circle")
Text("Return")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .ret ? .purple : .secondary)
Spacer()
Button (action: {
self.tabIdx = .device
}) {
VStack{
Image(systemName: "safari")
Text("Device")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .device ? .purple : .secondary)
Spacer()
Button (action: {
self.tabIdx = .you
}) {
VStack{
Image(systemName: "person.circle")
Text("You")
.font(.system(size: 10))
}
}
.foregroundColor(self.tabIdx == .you ? .purple : .secondary)
Spacer()
}
}
.padding(.bottom, 30)
.padding(.top, 10)
.background(Color(red: 0.95, green: 0.95, blue: 0.95))
.font(.system(size: 30))
.frame(height: 80)
}
}
struct ContentView: View {
#State var tabIdx: Tab = .borrow
var body: some View {
NavigationView {
VStack(spacing: 20) {
Spacer()
if tabIdx == .borrow {
Text("Borrow")
} else if tabIdx == .ret {
Text("Return")
} else if tabIdx == .device {
Text("Device")
} else if tabIdx == .you {
Text("You")
}
Spacer(minLength: 0)
TabView(tabIdx: self.$tabIdx)
.shadow(radius: 10)
}
.ignoresSafeArea()
}
}
}
Remember that when you do this, all your tabs are specified as cases within enum Tab {}, and the TabView() contains some Button elements, which will change the tab by using the #State var tabIdx. So you can adjust your actions by modifying the self.tabIdx = <yourtab> statement.
The active color is set with this statement after each button:
.foregroundColor(self.tabIdx == .borrow ? .purple : .secondary)
Here you can just change .purple to whatever suits you.
You can see that on the ContentView, the if-else if block catches the SubView. Here I have placed some Text() elements like Text("Borrow"), so you can replace them by calling something like BorrowView() or whatever you have.
I have added the shadow when I call the TabView from within the ContentView, but you could even add the .shadow(radius: 10) after the HStack of the TabView itself.
This would be the final output:
                 

Style the picker in SwiftUI

I'm doing a login form in Swiftui, however when i try to add a form that contain picker for age the style get messed up and since im new to SwiftUI i didn't know how can i fix this problem?
How can i fix the picker style so it addapte with the Nickname field?
here it is the code that i tried:
#State var nickName: String = ""
#State var age: Int = 18
var body: some View {
VStack {
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
TextField("Nickname", text: $nickName)
.frame(maxWidth: .infinity)
.frame(height: 50)
}
Divider().padding(.leading, 80)
HStack {
Image(systemName: "person.circle.fill")
.foregroundColor(Color("ChatiwVeryLightBlue"))
.frame(width: 44, height: 44)
.background(Color.white)
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.shadow(color: Color.black.opacity(0.15), radius: 5, x: 0, y: 5)
.padding(.all, 10)
Form {
Section {
Picker(selection: $age, label: Text("Picker")) {
ForEach(18 ..< 99, id: \.self) { index in
Text("\(index)").tag(1)
}
}
}
}
}
// Age
// Country
// Male/Female
}
.background(BlurView(blurType: .systemMaterial))
.clipShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
.padding()
.offset(y: 400)
Not sure if you are still working on this project, but here is a neat workaround:
struct ContentView: View {
#State private var age = 18
var body: some View {
NavigationView {
VStack {
Divider()
HStack {
NavigationLink(destination: AgePickerView(age: $age)) {
HStack {
Text("Age")
Spacer()
Text("\(age) >")
.padding(.trailing)
}
}
}
.foregroundColor(.gray)
Divider()
}
.padding(.leading, 80)
}
}
struct AgePickerView: View {
#Binding var age: Int
var body: some View {
List(18..<99) { index in
Button(action: {
self.age = index
}) {
HStack {
Text("\(index)")
Spacer()
if index == age {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}
}
}
}
}
To get the same NavigationView style but remove the Form style, you need to wrap a NavigationLink in a NavigationView.
The little > sign I put at the end of the Text view in the NavigationLink doesn't perfectly match the one in the Form style, so you can look around for an image that is more similar if you would like.
The AgePickerView is pretty straightfoward; it's just a list of buttons that are modifying a binding to the original state variable in ContentView through tap gestures. The checkmark is there to imitate a Picker view.
The best part of this is that you don't have to worry about all that index shifting with a picker.

Spacer Breaks Button Appearance, Button Still Toggles

I have created an HStack that houses a collection of buttons that change appearance when selected and deselected. Only one button can be selected at a time. When adding a Spacer() between items, the button toggle works, however the appearance no longer changes, even though the index selected is changing as it's supposed to.
struct GenericFilterButton: View {
var category: String
var textColor: Color
var buttonColor: Color
var body: some View {
Text(category)
.font(.custom("Avenir Heavy", size: 14))
.foregroundColor(textColor)
.padding(.vertical, 10)
.padding(.horizontal, 14)
.background(buttonColor)
.cornerRadius(50)
.lineLimit(1)
}
}
struct FilterViewCompletionPercent: View {
var completionPercent = ["Any", "25%", "50%", "75%", "100%"]
#State var percentSelected = 0
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text("Completion")
.font(.custom("Avenir Heavy", size: 18))
.foregroundColor(Color.black.opacity(0.5))
.padding(.bottom, 20)
HStack(alignment: .center) {
ForEach(0..<completionPercent.count) { i in
Button(action: {
self.percentSelected = i
print(self.percentSelected)
}) {
GenericFilterButton(category: self.completionPercent[i], textColor: self.percentSelected == i ? Color.white : Color.black.opacity(0.5), buttonColor: self.percentSelected == i ? Color.blue : Color.white.opacity(0.0))
}
if i != 4 {
Spacer()
}
}
}.padding(.bottom, 20)
Divider()
.background(Color.black.opacity(0.1))
}.padding(.horizontal, 25)
.padding(.top, 30)
}
}
Why may this be happening? I can't seem to think of a plausible reason why adding a Spacer() between items doesn't work, but when adding a Spacer() without the if statement does.
Dynamic content of ForEach should be single view, so embed your Button-Spacer pair into another HStack.
Tested with Xcode 11.4 / iOS 13.4
HStack(alignment: .center) {
ForEach(0..<completionPercent.count, id: \.self) { i in
HStack { // << this one !!
Button(action: {
self.percentSelected = i
print(self.percentSelected)
}) {
GenericFilterButton(category: self.completionPercent[i], textColor: self.percentSelected == i ? Color.white : Color.black.opacity(0.5), buttonColor: self.percentSelected == i ? Color.blue : Color.white.opacity(0.0))
}
if i != 4 {
Spacer()
}
}
}
}.padding(.bottom, 20)