How to change the offset of an view using onTapGesture? - ios

I am trying to move a view when it is being tapped. But unfortunately nothing happens. How do I toggle the position of it using the offset y axis?
struct Test: View {
#State var cards : [Card] = [Card(), Card(), Card(), Card(), Card(), Card()]
var body: some View {
VStack {
ZStack (alignment: .top){
ForEach (0..<cards.count) { i in
RoundedRectangle(cornerRadius: 10).frame(width: 250, height: 150)
.foregroundColor(Color.random).offset(y: self.cards[i].isFocus ? CGFloat(500) : CGFloat(i*50))
.zIndex(Double(i))
.onTapGesture {
self.cards[i].isFocus.toggle() // now the touched Card should move to an offset of y: 500, but nothing happens
}
}
}
Spacer()
}
}
}
struct Card :Identifiable{
#State var isFocus :Bool = false
var id = UUID()
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
extension Color {
static var random: Color {
return Color(red: .random(in: 0...1),
green: .random(in: 0...1),
blue: .random(in: 0...1))
}
}

I see your code and I am sorry to say but its wrong in many aspects. I would recommend you to go through SwiftUI basics and understand how and when we use #State, #ObservedObject, #Binding etc. Also always try to break your Views into the smallest components.
Below is the code which I think fulfills your requirement.
struct Test: View {
var cards : [Card] = [Card(), Card(), Card(), Card(), Card(), Card()]
var body: some View {
VStack {
ZStack (alignment: .top){
ForEach (0..<cards.count) { i in
CardView(card: self.cards[i], i: i)
}
}
Spacer()
}
}
}
struct CardView: View {
#ObservedObject var card: Card
let i: Int
var body: some View {
RoundedRectangle(cornerRadius: 10).frame(width: 250, height: 150)
.foregroundColor(Color.random)
.offset(y: card.isFocus ? CGFloat(500) : CGFloat(i*10))
.zIndex(Double(i))
.onTapGesture {
withAnimation {
self.card.isFocus.toggle()
}
}
}
}
class Card: ObservableObject{
#Published var isFocus: Bool = false
var id = UUID()
}

Related

how to infinite rotation in circular way by SwiftUI?

I could not rotate the green part in the circular path.
I want to set dynamically infinite time.
I created a struct for ProgressView and want to set it as ProgressView in my project.
how to implement the red and black colour curve design in the rotation
path
struct CircularProgressView: View {
#State var progressValue: Float = 0.0
var body: some View {
ZStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
}.onAppear{
self.incrementProgress()
}
}
func incrementProgress() {
let randomValue = Float([0.012, 0.022, 0.034, 0.016, 0.11, 0.012].randomElement()!)
self.progressValue += randomValue
}
}
struct ProgressBar: View {
#Binding var progress: Float
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.red)
Circle()
.trim(from: 0.0, to: CGFloat(min(self.progress, 2.0)))
.stroke(style: StrokeStyle(lineWidth: 20.0, lineCap: .round, lineJoin: .round))
.foregroundColor(Color.green)
.rotationEffect(Angle(degrees: 270))
.animation(.linear)
}
}
}
This is the Preview struct.
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
Thanks in advance for helping me.
struct CircularProgressView: View {
#State var progressValue: Float = 0.0
#State private var isLoaderVisible: Bool = false
var degree = 90
var body: some View {
ZStack {
ProgressBar(progress: self.$progressValue)
.frame(width: 150.0, height: 150.0)
.padding(40.0)
}.overlay(LoaderView(showLoader: isLoaderVisible))
.onAppear {
isLoaderVisible.toggle()
}
}
}
struct ProgressBar: View {
#Binding var progress: Float
#State var degree:Double = 90
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.foregroundColor(Color.green)
}
}
}
struct CircularProgressView_Previews: PreviewProvider {
static var previews: some View {
CircularProgressView()
}
}
// enum for LoaderView
public enum LoaderAnimation {
case low, medium, high
var animationSpeed: Double {
switch self {
case .low: return 1.0
case .medium: return 0.8
case .high: return 10.2
}
}
}
This this the LoaderView which we set on overlay and animate
public struct LoaderView: View {
var loaderAnimationSpeed: LoaderAnimation? = .high
var showLoader: Bool = false
public var body: some View {
GeometryReader { reader in
ZStack {
Circle()
.stroke(lineWidth: 20.0)
.opacity(0.3)
.foregroundColor(Color.yellow)
Circle()
.trim(from: 0.6, to: 1)
.stroke(.green, lineWidth: 20)
.rotationEffect(.degrees(showLoader ? 360 : 0))
.animation(Animation.easeInOut(duration: showLoader ? loaderAnimationSpeed?.animationSpeed ?? 0.0 : 1).repeatForever(autoreverses: false), value: showLoader)
}
}
}
public init( loaderAnimationSpeed: LoaderAnimation? = .medium, showLoader: Bool) {
self.loaderAnimationSpeed = loaderAnimationSpeed
self.showLoader = showLoader
}
}

SwiftUI : Why preferenceKey is not read/called

From the code below, I can't figure out why Preference key is not read from the view put in background of HStack. Strangely, If I put that BGViewSetter to the Text Views inside ForEach loop, it works. Unless I have ommitted something too obvious, it looks like a bug. Anybody can confirm it? Thanks
xcode12.2
ios 14.2
struct TestRectPreference : Equatable
{
var frame : CGRect
}
struct TestRectPreferenceKey : PreferenceKey
{
typealias Value = TestRectPreference
static var defaultValue: TestRectPreference = TestRectPreference(frame: CGRect.zero)
static func reduce(value: inout TestRectPreference, nextValue: () -> TestRectPreference)
{
value.frame = nextValue().frame
}
}
struct BGViewSetter: View {
var body: some View {
GeometryReader { geometry in
RoundedRectangle(cornerRadius: 25.0)
.fill(Color.init(#colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)))
.preference(key: TestRectPreferenceKey.self,
value: TestRectPreference(frame: geometry.frame(in: .global)))
}
}
}
struct FinalView : View
{
#State var offsetX : CGFloat = .zero
#State var info = ""
#State var size : CGFloat = .zero
var body: some View
{
VStack
{
Text("FinalView : \(info)")
HStack
{
ForEach( 1 ..< 10)
{ i in
Text("\(i)")
.frame(width: 100)
.opacity(0.8)
}
}
.background(BGViewSetter())
.animation(.easeOut)
.gesture(
DragGesture()
.onChanged
{ gesture in
self.offsetX = gesture.translation.width
}
.onEnded
{ _ in
self.offsetX = .zero
}
)
.offset(x: self.offsetX)
Text("Footer")
Divider()
Spacer()
}
.onPreferenceChange(TestRectPreferenceKey.self)
{
self.size = $0.frame.height
self.info = "Pref Chng : \(self.size)"
}
}
}
struct PreferenceKeyTest_Previews: PreviewProvider {
static var previews: some View {
FinalView()
}
}

SwiftUI Create a Custom Segmented Control also in a ScrollView

Below is my code to create a standard segmented control.
struct ContentView: View {
#State private var favoriteColor = 0
var colors = ["Red", "Green", "Blue"]
var body: some View {
VStack {
Picker(selection: $favoriteColor, label: Text("What is your favorite color?")) {
ForEach(0..<colors.count) { index in
Text(self.colors[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Value: \(colors[favoriteColor])")
}
}
}
My question is how could I modify it to have a customized segmented control where I can have the boarder rounded along with my own colors, as it was somewhat easy to do with UIKit? Has any one done this yet.
I prefect example is the Uber eats app, when you select a restaurant you can scroll to the particular portion of the menu by selecting an option in the customized segmented control.
Included are the elements I'm looking to have customized:
* UPDATE *
Image of the final design
Is this what you are looking for?
import SwiftUI
struct CustomSegmentedPickerView: View {
#State private var selectedIndex = 0
private var titles = ["Round Trip", "One Way", "Multi-City"]
private var colors = [Color.red, Color.green, Color.blue]
#State private var frames = Array<CGRect>(repeating: .zero, count: 3)
var body: some View {
VStack {
ZStack {
HStack(spacing: 10) {
ForEach(self.titles.indices, id: \.self) { index in
Button(action: { self.selectedIndex = index }) {
Text(self.titles[index])
}.padding(EdgeInsets(top: 16, leading: 20, bottom: 16, trailing: 20)).background(
GeometryReader { geo in
Color.clear.onAppear { self.setFrame(index: index, frame: geo.frame(in: .global)) }
}
)
}
}
.background(
Capsule().fill(
self.colors[self.selectedIndex].opacity(0.4))
.frame(width: self.frames[self.selectedIndex].width,
height: self.frames[self.selectedIndex].height, alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX)
, alignment: .leading
)
}
.animation(.default)
.background(Capsule().stroke(Color.gray, lineWidth: 3))
Picker(selection: self.$selectedIndex, label: Text("What is your favorite color?")) {
ForEach(0..<self.titles.count) { index in
Text(self.titles[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Value: \(self.titles[self.selectedIndex])")
Spacer()
}
}
func setFrame(index: Int, frame: CGRect) {
self.frames[index] = frame
}
}
struct CustomSegmentedPickerView_Previews: PreviewProvider {
static var previews: some View {
CustomSegmentedPickerView()
}
}
If I'm following the question aright the starting point might be something like the code below. The styling, clearly, needs a bit of attention. This has a hard-wired width for segments. To be more flexible you'd need to use a Geometry Reader to measure what was available and divide up the space.
struct ContentView: View {
#State var selection = 0
var body: some View {
let item1 = SegmentItem(title: "Some Way", color: Color.blue, selectionIndex: 0)
let item2 = SegmentItem(title: "Round Zip", color: Color.red, selectionIndex: 1)
let item3 = SegmentItem(title: "Multi-City", color: Color.green, selectionIndex: 2)
return VStack() {
Spacer()
Text("Selected Item: \(selection)")
SegmentControl(selection: $selection, items: [item1, item2, item3])
Spacer()
}
}
}
struct SegmentControl : View {
#Binding var selection : Int
var items : [SegmentItem]
var body : some View {
let width : CGFloat = 110.0
return HStack(spacing: 5) {
ForEach (items, id: \.self) { item in
SegmentButton(text: item.title, width: width, color: item.color, selectionIndex: item.selectionIndex, selection: self.$selection)
}
}.font(.body)
.padding(5)
.background(Color.gray)
.cornerRadius(10.0)
}
}
struct SegmentButton : View {
var text : String
var width : CGFloat
var color : Color
var selectionIndex = 0
#Binding var selection : Int
var body : some View {
let label = Text(text)
.padding(5)
.frame(width: width)
.background(color).opacity(selection == selectionIndex ? 1.0 : 0.5)
.cornerRadius(10.0)
.foregroundColor(Color.white)
.font(Font.body.weight(selection == selectionIndex ? .bold : .regular))
return Button(action: { self.selection = self.selectionIndex }) { label }
}
}
struct SegmentItem : Hashable {
var title : String = ""
var color : Color = Color.white
var selectionIndex = 0
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
None of the above solutions worked for me as the GeometryReader returns different values once placed in a Navigation View that throws off the positioning of the active indicator in the background. I found alternate solutions, but they only worked with fixed length menu strings. Perhaps there is a simple modification to make the above code contributions work, and if so, I would be eager to read it. If you're having the same issues I was, then this may work for you instead.
Thanks to inspiration from a Reddit user "End3r117" and this SwiftWithMajid article, https://swiftwithmajid.com/2020/01/15/the-magic-of-view-preferences-in-swiftui/, I was able to craft a solution. This works either inside or outside of a NavigationView and accepts menu items of various lengths.
struct SegmentMenuPicker: View {
var titles: [String]
var color: Color
#State private var selectedIndex = 0
#State private var frames = Array<CGRect>(repeating: .zero, count: 5)
var body: some View {
VStack {
ZStack {
HStack(spacing: 10) {
ForEach(self.titles.indices, id: \.self) { index in
Button(action: {
print("button\(index) pressed")
self.selectedIndex = index
}) {
Text(self.titles[index])
.foregroundColor(color)
.font(.footnote)
.fontWeight(.semibold)
}
.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 5))
.modifier(FrameModifier())
.onPreferenceChange(FramePreferenceKey.self) { self.frames[index] = $0 }
}
}
.background(
Rectangle()
.fill(self.color.opacity(0.4))
.frame(
width: self.frames[self.selectedIndex].width,
height: 2,
alignment: .topLeading)
.offset(x: self.frames[self.selectedIndex].minX - self.frames[0].minX, y: self.frames[self.selectedIndex].height)
, alignment: .leading
)
}
.padding(.bottom, 15)
.animation(.easeIn(duration: 0.2))
Text("Value: \(self.titles[self.selectedIndex])")
Spacer()
}
}
}
struct FramePreferenceKey: PreferenceKey {
static var defaultValue: CGRect = .zero
static func reduce(value: inout CGRect, nextValue: () -> CGRect) {
value = nextValue()
}
}
struct FrameModifier: ViewModifier {
private var sizeView: some View {
GeometryReader { geometry in
Color.clear.preference(key: FramePreferenceKey.self, value: geometry.frame(in: .global))
}
}
func body(content: Content) -> some View {
content.background(sizeView)
}
}
struct NewPicker_Previews: PreviewProvider {
static var previews: some View {
VStack {
SegmentMenuPicker(titles: ["SuperLongValue", "1", "2", "Medium", "AnotherSuper"], color: Color.blue)
NavigationView {
SegmentMenuPicker(titles: ["SuperLongValue", "1", "2", "Medium", "AnotherSuper"], color: Color.red)
}
}
}
}

SwiftUI: Transition won't slide

I have control with an edit and a list in a Stack. I want the list to drop down from top to bottom to animate like a fancy menu. However I am having a few issues. If I just try to use the move transition nothing happens. If I use the scale transition it always scales form center never from top down. This was just trying to get the transition overridden to slide. Anything but fade.
My control looks like so
struct Search: Identifiable {
let id: UUID
let text: String
}
struct SearchBox: View {
#State var searchParam: String = ""
#State var stuff = [Search]()
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
var binding = Binding<String>(
get: {
self.searchParam
},
set: {
self.stuff.append(
Search(id: UUID(), text: $0))
self.searchParam = $0
})
return VStack(spacing: 0.0) {
TextField("Search", text: binding )
.font(.title)
.padding()
.background(Color.white)
Color(.darkGray)
.frame(height: 1.0)
if stuff.count > 0 {
List(stuff, id: \.id) {
Text($0.text)
}
.transition(.slide)
}
}
}
struct SearchBox_Preview: PreviewProvider {
static var previews: some View{
SearchBox()
}
}
}
The content view is simple..
struct ContentView: View {
var body: some View {
GeometryReader { geometry in
ZStack(alignment: .topLeading) {
Color.blue
SearchBox()
.frame(width: geometry.size.width * 0.40, alignment: .topLeading)
.frame(minHeight: 0, maxHeight: geometry.size.height * 0.40,
alignment: .topLeading)
.padding()
.clipped()
.shadow(radius: 5.0)
}
.background(Color.clear)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I have to be missing, or not getting, something simple
Answered my own question. We need to force an animation by using withAnimation on a state. So I have changed my biding to be like so:
struct Search: Identifiable {
let id: UUID
let text: String
}
struct SearchBox: View {
#State var searchParam: String = ""
#State var stuff = [Search]()
#State var showList = false
init() {
// To remove only extra separators below the list:
UITableView.appearance().tableFooterView = UIView()
// To remove all separators including the actual ones:
UITableView.appearance().separatorStyle = .none
}
var body: some View {
var binding = Binding<String>(
get: {
self.searchParam
},
set: {
self.stuff.append(
Search(id: UUID(), text: $0)
)
self.searchParam = $0
// change visibility state with in animation block.
withAnimation { self.showList = stuff.count > 0 }
})
return VStack(spacing: 0.0) {
TextField("Search", text: binding )
.font(.title)
.padding()
.background(Color.white)
Color(.darkGray)
.frame(height: 1.0)
if showList {
List(stuff, id: \.id) {
Text($0.text)
}
.transition(.slide)
}
}
}
struct SearchBox_Preview: PreviewProvider {
static var previews: some View{
SearchBox()
}
}
}

Flat navigation best practice in SwiftUI

So I was reading about the Navigation in the Apple UI guidlines. In SwiftUI, for hierarchical navigation we use the NavigationLink-View. Flat navigation is for example the view for each song in apple music.
My questions:
I have a solution but I wonder what is the best practice solution.
Furthermore, I have to add the animation myself, whereas with NavigationLink I get it for "free". Why does the transition not work? I'm trying to get a similar animation as in apple music when skipping a song.
import SwiftUI
struct Song:Identifiable{
var id = UUID()
var name:String
var cover = Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1))
}
struct ContentView: View {
var songs:[Song] = []
init(){
for i in 0...1000{
self.songs.append(Song(name: "Song \(i)"))
}
}
#State var index = 0
var body: some View {
VStack{
DetailView(song:songs[index])
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .trailing)))
SongNavigation(index:$index, totalSongs: songs.count)
}.padding()
}
}
struct DetailView: View{
var song:Song
var body: some View{
VStack{
Text(song.name)
}.frame(minWidth:0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity).background(song.cover)
}
}
struct SongNavigation:View{
#Binding var index:Int
var totalSongs:Int
var body: some View{
HStack{
Button("previous"){
withAnimation(){
if self.index > 0{
self.index -= 1
}
}
}
Spacer()
Button("next"){
withAnimation(){
if self.index != self.totalSongs-1 {
self.index += 1
}
}
}
}
}
}

Resources