Animating a flashing bell in SwiftUI - swift

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.

Related

ScrollView stops components from expanding

I would like to have my cards expandable and fill the while area of the screen while they are doing the change form height 50 to the whole screen (and don't display the other components)
Here is my code:
import SwiftUI
struct DisciplineView: View {
var body: some View {
ScrollView(showsIndicators: false) {
LazyVStack {
Card(cardTitle: "Notes")
Card(cardTitle: "Planner")
Card(cardTitle: "Homeworks / Exams")
}
.ignoresSafeArea()
}
}
}
struct DisciplineV_Previews: PreviewProvider {
static var previews: some View {
DisciplineView()
}
}
import SwiftUI
struct Card: View {
#State var cardTitle = ""
#State private var isTapped = false
var body: some View {
RoundedRectangle(cornerRadius: 30, style: .continuous)
.stroke(style: StrokeStyle(lineWidth: 5, lineCap: .round, lineJoin: .round))
.foregroundColor(.gray.opacity(0.2))
.frame(width: .infinity, height: isTapped ? .infinity : 50)
.background(
VStack {
cardInfo
if(isTapped) { Spacer() }
}
.padding(isTapped ? 10 : 0)
)
}
var cardInfo: some View {
HStack {
Text(cardTitle)
.font(.title).bold()
.foregroundColor(isTapped ? .white : .black)
.padding(.leading, 10)
Spacer()
Image(systemName: isTapped ? "arrowtriangle.up.square.fill" : "arrowtriangle.down.square.fill")
.padding(.trailing, 10)
.onTapGesture {
withAnimation {
isTapped.toggle()
}
}
}
}
}
struct Card_Previews: PreviewProvider {
static var previews: some View {
Card()
}
}
here is almost the same as I would like to have, but I would like the first one to be on the whole screen and stop the ScrollView while appearing.
Thank you!
Described above:
I would like to have my cards expandable and fill the while area of the screen while they are doing the change form height 50 to the whole screen (and don't display the other components)
I think this is pretty much what you are trying to achieve.
Basically, you have to scroll to the position of the recently presented view and disable the scroll. The scroll have to be disabled enough time to avoid continuing to the next item but at the same time, it have to be enabled soon enough to give the user the feeling that it is scrolling one item at once.
struct ContentView: View {
#State private var canScroll = true
#State private var itemInScreen = -1
var body: some View {
GeometryReader { geo in
ScrollViewReader { proxy in
ScrollView {
LazyVStack {
ForEach(0...10, id: \.self) { item in
Text("\(item)")
.onAppear {
withAnimation {
proxy.scrollTo(item)
canScroll = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
canScroll = true
}
}
}
}
.frame(width: geo.size.width, height: geo.size.height)
.background(Color.blue)
}
}
}
.disabled(!canScroll)
}
.ignoresSafeArea()
}
}

View don't disappear with the way it should

I am trying to make the yellow views disappear from bottom and top with a nice and smooth animation. (slide/move to top/bottom + fadding and keep the middle view taking the whole space)
This is my current state, and it is everything but smooth and nice haha. but it works.
import SwiftUI
struct ContentView: View {
#State var isInterfaceHidden: Bool = false
var body: some View {
VStack(spacing: 0, content: {
if !isInterfaceHidden {
Rectangle()
.id("animation")
.foregroundColor(Color.yellow)
.frame(height: 40)
.transition(AnyTransition.move(edge: .top).combined(with: .opacity).animation(.linear))
}
Rectangle()
.id("animation2")
.foregroundColor(Color.red)
.transition(AnyTransition.opacity.animation(Animation.linear))
/// We make sure it won't cover the top and bottom view.
.zIndex(-1)
.background(Color.red)
.onTapGesture(perform: {
DispatchQueue.main.async {
self.isInterfaceHidden.toggle()
}
})
if !isInterfaceHidden {
Rectangle()
.id("animation3")
.foregroundColor(Color.yellow)
.frame(height: 80)
.transition(AnyTransition.move(edge: .bottom).combined(with: .opacity).animation(.linear))
}
})
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
Current animation:
Edit3: Thanks to #Andrew I was able to progress in a better state.
But now I have a sort of jerky animation.
Any thoughts?
I may have found a solution for you:
import SwiftUI
struct ContentView: View {
#State var isInterfaceHidden: Bool = false
var body: some View {
VStack(spacing: 0, content: {
if !isInterfaceHidden {
Rectangle()
.id("animation")
.foregroundColor(Color.yellow)
.frame(height: 40)
.transition(.topViewTransition)
}
Rectangle()
.id("animation2")
.foregroundColor(Color.red)
/// We make sure it won't cover the top and bottom view.
.zIndex(-1)
.background(Color.red)
.onTapGesture(perform: {
DispatchQueue.main.async {
self.isInterfaceHidden.toggle()
}
})
if !isInterfaceHidden {
Rectangle()
.id("animation3")
.foregroundColor(Color.yellow)
.frame(height: 80)
.transition(.bottomViewTransition)
}
})
.navigationBarTitle("")
.navigationBarHidden(true)
.animation(.easeInOut)
}
}
extension AnyTransition {
static var topViewTransition: AnyTransition {
let transition = AnyTransition.move(edge: .top)
.combined(with: .opacity)
return transition
}
static var bottomViewTransition: AnyTransition {
let transition = AnyTransition.move(edge: .bottom)
.combined(with: .opacity)
return transition
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Simply set Z index for the both of your yellow views anything higher than the default value of 1.0. This way SwiftUI will make sure they won't be covered by the red view.
The modifier to do that is .zIndex()
A simpler solution
struct Test: View {
#State private var isHiding = false
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.yellow)
.frame(width: 200, height: 100)
Rectangle()
.foregroundColor(.red)
.frame(width: 200, height: isHiding ? 100 : 80)
.onTapGesture {
withAnimation {
self.isHiding.toggle()
}
}
}
}
}

Dynamic row height in a SwiftUI form

I'm adding controls to a SwiftUI Form to assist the user enter data (and constrain the entries!). Although there is a lot to like about Forms, I've discovered that things that work nicely outside this container do very unexpected things inside it and it's not always obvious how to compensate for this.
The plan is to have the data field displayed as a single row. When the row is tapped, the control slides out from behind the data field - the row will need to expand (height) to accommodate the control.
I'm using Swift Playgrounds to develop the proof of concept (or failure in my case). The idea is to use a ZStack which will allow a nice sliding animation by overlaying the views and giving them a different zIndex and applying the offset when the data field view is tapped. Sounds simple but of course the Form row does not expand when the ZStack is expanded.
Adjusting the frame of the ZStack while expanding causes all sorts of weird changes in padding (or at least it looks like it) which can be compensated for by counter-offsetting the "top" view but this causes other unpredictable behaviour. Pointers and ideas gratefully accepted.
import SwiftUI
struct MyView: View {
#State var isDisclosed = false
var body: some View {
Form {
Spacer()
VStack {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.zIndex(1)
.onTapGesture { self.isDisclosed.toggle() }
Rectangle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.offset(y: isDisclosed ? 50 : 0)
.animation(.easeOut)
}
}
Spacer()
}
}
}
Collapsed stack
Expanded stack - view overlaps adjacent row
Result when adjusting ZStack vertical frame when expanded - top padding increases
Here is possible solution with fluent row height change (using AnimatingCellHeight modifier taken from my solution in SwiftUI - Animations triggered inside a View that's in a list doesn't animate the list as well ).
Tested with Xcode 11.4 / iOS 13.4
struct MyView: View {
#State var isDisclosed = false
var body: some View {
Form {
Spacer()
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.zIndex(1)
.onTapGesture { withAnimation { self.isDisclosed.toggle() } }
HStack {
Rectangle()
.fill(Color.blue)
.frame(width: 100, height: 100)
}.frame(maxHeight: .infinity, alignment: .bottom)
}
.modifier(AnimatingCellHeight(height: isDisclosed ? 150 : 100))
Spacer()
}
}
}
Use alignmentGuide instead of offset.
...
//.offset(y: isDisclosed ? 50 : 0)
.alignmentGuide(.top, computeValue: { dimension in dimension[.top] - (self.isDisclosed ? 50 : 0) })
...
offset doesn't affect its view's frame. that's why Form doesn't react as expected. On the contrary, alignmentGuide does.
I now have a working implementation using alignment guides as suggested by Kyokook. I have softened the somewhat jarring row height change by adding an opacity animation to the Stepper as it slides out. This also helps to prevent a slightly glitchy overlap of the row title when the control is closed.
struct ContentView: View {
// MARK: Logic state
#State private var years = 0
#State private var months = 0
#State private var weeks = 0
// MARK: UI state
#State var isStepperVisible = false
var body: some View {
Form {
Text("Row 1")
VStack {
// alignment guide must be explicit for the ZStack & all child ZStacks
// must use the same alignment guide - weird stuff happens otherwise
ZStack(alignment: .top) {
HStack {
Text("AGE")
.bold()
.font(.footnote)
Spacer()
Text("\(years) years \(months) months \(weeks) weeks")
.foregroundColor(self.isStepperVisible ? Color.blue : Color.gray)
}
.frame(height: 35) // TODO: Without this, text in HStack vertically offset. Investigate. (HStack align doesn't help)
.background(Color.white) // Prevents overlap of text during transition
.zIndex(3)
.contentShape(Rectangle())
.onTapGesture {
self.isStepperVisible.toggle()
}
HStack(alignment: .center) {
StepperComponent(value: $years, label: "Years", bounds: 0...30, isVisible: $isStepperVisible)
StepperComponent(value: $months, label: "Months", bounds: 0...12, isVisible: $isStepperVisible)
StepperComponent(value: $weeks, label: "Weeks", bounds: 0...4, isVisible: $isStepperVisible)
}
.alignmentGuide(.top, computeValue: { dimension in dimension[.top] - (self.isStepperVisible ? 40 : 0) })
}
}
Text("Row 3")
}
}
}
struct StepperComponent<V: Strideable>: View {
// MARK: Logic state
#Binding var value: V
var label: String
var bounds: ClosedRange<V>
//MARK: UI state
#Binding var isVisible: Bool
var body: some View {
ZStack(alignment: .top) {
Text(label.uppercased()).font(.caption).bold()
.frame(alignment: .center)
.zIndex(1)
.opacity(self.isVisible ? 1 : 0)
.animation(.easeOut)
Stepper(label, value: self.$value, in: bounds)
.labelsHidden()
.alignmentGuide(.top, computeValue: { dimension in dimension[.top] - (self.isVisible ? 25 : 0) })
.frame(alignment: .center)
.zIndex(2)
.opacity(self.isVisible ? 1 : 0)
.animation(.easeOut)
}
}
}
There is still some room for improvement here but on the whole I'm pleased with the result :-)
Thanks to both Kyokook (for putting me straight on offset()) and Asperi.
I think the Kyokook's solution (using AlignmentGuides) is simpler and would be my preference in that it's leveraging Apple's existing API and seems to cause less unpredictable movement of the views in their container. However, the row height changes abruptly and isn't synchronised. The animation in the Asperi's example is smoother but there is some bouncing of the views within the row (it's almost as if the padding or insets are changing and then being reset at the end of the animation). My approach to animation is a bit hit-and-miss so any further comments would be welcome.
Solution 1 (frame consistent, animation choppy):
struct ContentView: View {
#State var isDisclosed = false
var body: some View {
Form {
Text("Row 1")
VStack {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.zIndex(1)
.onTapGesture {
self.isDisclosed.toggle()
}
Rectangle()
.fill(Color.blue)
.frame(width: 100, height: 100)
.alignmentGuide(.top, computeValue: { dimension in dimension[.top] - (self.isDisclosed ? 100 : 0) })
.animation(.easeOut)
Text("Row 3")
}
}
Text("Row 3")
}
}
}
Solution 2 (smoother animation but frame variance):
struct ContentView: View {
#State var isDisclosed = false
var body: some View {
Form {
Text("Row 1")
VStack {
ZStack(alignment: .topLeading) {
Rectangle()
.fill(Color.red)
.frame(width: 100, height: 100)
.zIndex(1)
.onTapGesture {
withAnimation { self.isDisclosed.toggle() }
}
HStack {
Rectangle()
.fill(Color.blue)
.frame(width: 100, height: 100)
}.frame(maxHeight: .infinity, alignment: .bottom)
}
.modifier(AnimatingCellHeight(height: isDisclosed ? 200 : 100))
}
Text("Row 3")
}
}
}
struct AnimatingCellHeight: AnimatableModifier {
var height: CGFloat = 0
var animatableData: CGFloat {
get { height }
set { height = newValue }
}
func body(content: Content) -> some View {
content.frame(height: height)
}
}

Modal picker not scrolling right SwiftUI

I created a modal but it seems to have a bug on the selection. When scrolling the left, it scrolls the right, I have to go to the very edge of the left to be able to scroll, this is how it looks:
import SwiftUI
struct ContentView: View {
#State var showingModal = false
#State var hours: Int = 0
#State var minutes: Int = 0
var body: some View {
ZStack {
VStack {
Button("Show me"){
self.showingModal = true
}
if $showingModal.wrappedValue {
VStack(alignment: .center) {
ZStack{
Color.black.opacity(0.4)
.edgesIgnoringSafeArea(.vertical)
// this one is it
VStack(spacing: 20) {
Text("Time between meals")
.bold().padding()
.frame(maxWidth: .infinity)
.background(Color.yellow)
.foregroundColor(Color.white)
HStack {
Spacer()
VStack {
Picker("", selection: $hours){
ForEach(0..<4, id: \.self) { i in
Text("\(i) hours").tag(i)
}
}
.frame(width: 150, height: 120)
.clipped()
}
VStack {
Picker("", selection: $minutes){
ForEach(0..<60, id: \.self) { i in
Text("\(i) min").tag(i)
}
}
.frame(width: 150, height: 120)
.clipped()
}
}
Spacer()
Button(action: {
self.showingModal = false
}){
Text("Close")
} .padding()
}
.frame(width:300, height: 300)
.background(Color.white)
.cornerRadius(20).shadow(radius: 20)
}
}
}
}
}
}
}
How can I fix that little bug? I tried playing around with the layout but no use... any help would be appreciated
What if I told you the reason your Picker not working was this line?
.cornerRadius(20).shadow(radius: 20)
Unfortunately, SwiftUI is still quite buggy and sometimes it doesn't do what it is supposed to do and especially Pickers are not that reliable. I guess we'll need to wait and see the next iteration of SwiftUI, but for now you can replace that line with the code below:
.mask(RoundedRectangle(cornerRadius: 20))
.shadow(radius: 20)
There are just modifiers which affect all view hierarchy (ie. all subviews) that can change resulting layout/presentation/behaviour. And .cornerRadius and .shadow are such type modifiers.
The solution is to apply (as intended) those modifiers only to entire constructed view, and here it is
.compositingGroup() // <<< fix !!
.cornerRadius(20).shadow(radius: 20)
where .compositionGroup is intended to make above view hierarchy flat rendered and all below modifiers applied to only to that flat view.

Adding Segmented Style Picker to SwiftUI's NavigationView

The question is as simple as in the title. I am trying to put a Picker which has the style of SegmentedPickerStyle to NavigationBar in SwiftUI. It is just like the native Phone application's history page. The image is below
I have looked for Google and Github for example projects, libraries or any tutorials and no luck. I think if nativa apps and WhatsApp for example has it, then it should be possible. Any help would be appreciated.
SwiftUI 2 + toolbar:
struct DemoView: View {
#State private var mode: Int = 0
var body: some View {
Text("Hello, World!")
.toolbar {
ToolbarItem(placement: .principal) {
Picker("Color", selection: $mode) {
Text("Light").tag(0)
Text("Dark").tag(1)
}
.pickerStyle(SegmentedPickerStyle())
}
}
}
}
You can put a Picker directly into .navigationBarItems.
The only trouble I'm having is getting the Picker to be centered. (Just to show that a Picker can indeed be in the Navigation Bar I put together a kind of hacky solution with frame and Geometry Reader. You'll need to find a proper solution to centering.)
struct ContentView: View {
#State private var choices = ["All", "Missed"]
#State private var choice = 0
#State private var contacts = [("Anna Lisa Moreno", "9:40 AM"), ("Justin Shumaker", "9:35 AM")]
var body: some View {
GeometryReader { geometry in
NavigationView {
List {
ForEach(self.contacts, id: \.self.0) { (contact, time) in
ContactView(name: contact, time: time)
}
.onDelete(perform: self.deleteItems)
}
.navigationBarTitle("Recents")
.navigationBarItems(
leading:
HStack {
Button("Clear") {
// do stuff
}
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[$0])
}
}
.frame(width: 130)
.pickerStyle(SegmentedPickerStyle())
.padding(.leading, (geometry.size.width / 2.0) - 130)
},
trailing: EditButton())
}
}
}
func deleteItems(at offsets: IndexSet) {
contacts.remove(atOffsets: offsets)
}
}
struct ContactView: View {
var name: String
var time: String
var body: some View {
HStack {
VStack {
Image(systemName: "phone.fill.arrow.up.right")
.font(.headline)
.foregroundColor(.secondary)
Text("")
}
VStack(alignment: .leading) {
Text(self.name)
.font(.headline)
Text("iPhone")
.foregroundColor(.secondary)
}
Spacer()
Text(self.time)
.foregroundColor(.secondary)
}
}
}
For those who want to make it dead center, Just put two HStack to each side and made them width fixed and equal.
Add this method to View extension.
extension View {
func navigationBarItems<L, C, T>(leading: L, center: C, trailing: T) -> some View where L: View, C: View, T: View {
self.navigationBarItems(leading:
HStack{
HStack {
leading
}
.frame(width: 60, alignment: .leading)
Spacer()
HStack {
center
}
.frame(width: 300, alignment: .center)
Spacer()
HStack {
//Text("asdasd")
trailing
}
//.background(Color.blue)
.frame(width: 100, alignment: .trailing)
}
//.background(Color.yellow)
.frame(width: UIScreen.main.bounds.size.width-32)
)
}
}
Now you have a View modifier which has the same usage of navigationBatItems(:_). You can edit the code based on your needs.
Usage example:
.navigationBarItems(leading: EmptyView(), center:
Picker(selection: self.$choice, label: Text("Pick One")) {
ForEach(0 ..< self.choices.count) {
Text(self.choices[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
}, trailing: EmptyView())
UPDATE
There was the issue of leading and trailing items were violating UINavigationBarContentView's safeArea. While I was searching through, I came across another solution in this answer. It is little helper library called SwiftUIX. If you do not want install whole library -like me- I created a gist just for navigationBarItems. Just add the file to your project.
But do not forget this, It was stretching the Picker to cover all the free space and forcing StatusView to be narrower. So I had to set frames like this;
.navigationBarItems(center:
Picker(...) {
...
}
.frame(width: 150)
, trailing:
StatusView()
.frame(width: 70)
)
If you need segmentcontroll to be in center you need to use GeometryReader, below code will provide picker as title, and trailing (right) button.
You set up two view on the sides left and right with the same width, and the middle view will take the rest.
5 is the magic number depends how width you need segment to be.
You can experiment and see the best fit for you.
GeometryReader {
Text("TEST")
.navigationBarItems(leading:
HStack {
Spacer().frame(width: geometry.size.width / 5)
Spacer()
picker
Spacer()
Button().frame(width: geometry.size.width / 5)
}.frame(width: geometry.size.width)
}
But better solution is if you save picker size and then calculate other frame sizes, so picker will be same on ipad & iphone
#State var segmentControllerWidth: CGFloat = 0
var body: some View {
HStack {
Spacer()
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
.background(Color.red)
segmentController
.fixedSize()
.background(PreferenceViewSetter())
profileButton
.frame(width: (geometry.size.width / 2) - (segmentControllerWidth / 2))
}
.onPreferenceChange(PreferenceViewKey.self) { preferences in
segmentControllerWidth = preferences.width
}
}
struct PreferenceViewSetter: View {
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: PreferenceViewKey.self,
value: PreferenceViewData(width: geometry.size.width))
}
}
}
struct PreferenceViewData: Equatable {
let width: CGFloat
}
struct PreferenceViewKey: PreferenceKey {
typealias Value = PreferenceViewData
static var defaultValue = PreferenceViewData(width: 0)
static func reduce(value: inout PreferenceViewData, nextValue: () -> PreferenceViewData) {
value = nextValue()
}
}
Simple answer how to center segment controller and hide one of the buttons.
#State var showLeadingButton = true
var body: some View {
HStack {
Button(action: {}, label: {"leading"})
.opacity(showLeadingButton ? true : false)
Spacer()
Picker(selection: $selectedStatus,
label: Text("SEGMENT") {
segmentValues
}
.id(UUID())
.pickerStyle(SegmentedPickerStyle())
.fixedSize()
Spacer()
Button(action: {}, label: {"trailing"})
}
.frame(width: UIScreen.main.bounds.width)
}