How to draw circles on random position in view borders - ios

Help me please to catch how to draw circles on random position in view borders.
Red View options
.background(Color.red)
.padding(40)
Circles:
var body: some View
{
Circle()
.strokeBorder(Color.blue, lineWidth: 2.0, antialiased: true)
.frame(width: 50, height: 50, alignment: .center)
.overlay(Text(title))
}
I try to add circles on red view in random position using .position option, but you can see its not working:
The main problem is how to know width and height of red view on any device and set .position of circles.

You can use GeometryReader for reading the size of View, like in the example:
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
ForEach(0...50, id:\.self) { index in
CirclesView(index: index)
.offset(x: CGFloat.random(in: 0.0...proxy.size.width), y: CGFloat.random(in: 0.0...proxy.size.height))
}
}
.background(Color.red)
.ignoresSafeArea()
}
}
struct CirclesView: View {
let index: Int
var body: some View {
Circle()
.strokeBorder(Color.blue, lineWidth: 2.0, antialiased: true)
.frame(width: 50, height: 50, alignment: .center)
.overlay(Text(String(describing: index)))
}
}
Update:
struct ContentView: View {
var body: some View {
GeometryReader { proxy in
ForEach(0...50, id:\.self) { index in
CirclesView(index: index, offset: logicalFunction(size: proxy.size))
}
}
.background(Color.red)
.ignoresSafeArea()
}
func logicalFunction(size: CGSize) -> CGSize {
// Do your works here!
let width: CGFloat = CGFloat.random(in: 0.0...size.width)
let height: CGFloat = CGFloat.random(in: 0.0...size.height)
return CGSize(width: width, height: height)
}
}
struct CirclesView: View {
let index: Int
let offset: CGSize
var body: some View {
Circle()
.strokeBorder(Color.blue, lineWidth: 2.0, antialiased: true)
.frame(width: 50, height: 50, alignment: .center)
.overlay(Text(String(describing: index)))
.offset(offset)
}
}

Related

How do I add a vertical swipe up gesture(to go to a different view) in a scrollview in SwiftUI

I designed a SwiftUI view which is a scrollview. Now I need to add a vertical swipe gesture to it which shall take it to a different view. I tried to do it using the tabView and adding a rotating effect of -90 degrees to it. But that rotates my original view too and that's not what I want. I couldn't find any relevant help in SwiftUI which deals with swiping up a scrollview to a new view.
Here's my code..
the vertical swipe I achieved using this. But my view get rotated. Setting other angles disappears the view somehow. I am new to SwiftUI, I am stuck on it for a week now.1
GeometryReader { proxy in
TabView {
ScrollView {
VStack(alignment: .center) {
ZStack(alignment: .leading) {
Image("Asset 13").resizable().frame(width: percentWidth(percentage: 100), height: percentHeight(percentage: 50), alignment: .top)
HStack {
Spacer()
Image("Asset 1")//.padding(.bottom, 130)
Spacer()
}.padding(.bottom, 150)
HStack {
VStack(spacing:2) {
Text("followers").foregroundColor(.white).padding(.leading, 20)
HStack {
Image("Asset 3")
Text("10.5k").foregroundColor(.white)
}
}
Spacer()
VStack {
Image("Asset 10").padding(.trailing)
Text("300K Review ").foregroundColor(.white)
}
}.background(Image("Asset 2").resizable().frame(width: percentWidth(percentage: 100), height: percentHeight(percentage: 6), alignment: .leading))
.padding(.top, 410)
HStack {
Spacer()
Image("Asset 14").resizable().frame(width: percentWidth(percentage: 50), height: percentHeight(percentage: 25), alignment: .center)
Spacer()
}.padding(.top, 390)
}
VStack(spacing: 4) {
Text("Karuna Ahuja | Yoga Instructor").font(Font.custom(FontName.bold, size: 22))
Text("12 Years of Experience with Bhartiya Yog Sansthan").tracking(-1).font(Font.custom(FontName.light, size: 16)).opacity(0.4)
}
Divider()
HStack {
ZStack {
Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
VStack {
Image("Asset 5").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
Text("245").font(Font.custom(FontName.bold, size: 16))
Text("Video").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
}
}
ZStack {
Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
VStack {
Image("Asset 7").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
Text("45").font(Font.custom(FontName.bold, size: 16))
Text("Live Class").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
}
}
ZStack {
Image("Asset 6").resizable().frame(width: percentWidth(percentage: 30), height: percentHeight(percentage: 12), alignment: .center)
VStack {
Image("Asset 9").resizable().frame(width: percentWidth(percentage: 8), height: percentHeight(percentage: 4), alignment: .center)
Text("245").font(Font.custom(FontName.bold, size: 16))
Text("Sessions").font(Font.custom(FontName.medium, size: 16)).opacity(0.5)
}
}
}
Divider()
Text("Shine bright so that your light leads other. I'm a fitness junkie, high-energy yoga instructor. Let's make fitness FUN!").font(Font.custom(FontName.normal, size: 16)).tracking(-1).opacity(0.7).padding([.leading,.trailing], 6)
VideoPlayer(player: AVPlayer(url: videoUrl))
.frame(height: 320)
Spacer()
}.gesture(DragGesture(minimumDistance: 20, coordinateSpace: .global)
.onEnded { value in
let horizontalAmount = value.translation.width as CGFloat
let verticalAmount = value.translation.height as CGFloat
if abs(horizontalAmount) > abs(verticalAmount) {
print(horizontalAmount < 0 ? "left swipe" : "right swipe")
} else {
print(verticalAmount < 0 ? "up swipe" : "down swipe")
}
})
}.edgesIgnoringSafeArea(.all)
.ignoresSafeArea()
Text("this")
Text("this")
Text("this")
// ForEach(colors, id: \.self) { color in
// color // Your cell content
// }
// .rotationEffect(.degrees(-90)) // Rotate content
// .frame(
// width: proxy.size.width,
// height: proxy.size.height
// )
}
.frame(
width: proxy.size.height, // Height & width swap
height: proxy.size.width
)
.rotationEffect(.degrees(90), anchor: .topLeading) // Rotate TabView
.offset(x: proxy.size.width) // Offset back into screens bounds
.tabViewStyle(
PageTabViewStyle(indexDisplayMode: .never)
)
}
The only pure SwiftUI way I see is to do your own ScrollView implementation, which is not too complicated. This example has two views on top of each other. If you drag the first view further up than to the middle of the screen, it swipes away to reveal the second view.
struct ContentView: View {
#State private var offset = CGFloat.zero
#State private var dragOffset = CGFloat.zero
#State private var viewHeight = CGFloat.zero
var body: some View {
GeometryReader { fullgeo in
ZStack(alignment: .top) {
SecondView()
// necessary for second view to resize individually
.frame(height: fullgeo.size.height)
ScrollingView()
.overlay( GeometryReader { geo in Color.clear.onAppear { viewHeight = geo.size.height }})
.offset(y: offset + dragOffset)
.gesture(DragGesture()
.onChanged { value in
dragOffset = value.translation.height
}
.onEnded { value in
withAnimation(.easeOut) {
dragOffset = .zero
offset += value.predictedEndTranslation.height
// if bottom dragged higher than 50% of screen > second view
if offset < -(viewHeight - fullgeo.size.height/2) {
dragOffset = -viewHeight
return
}
// else constrain to top / bottom of ScrollingView
offset = max(min(offset, 0), -(viewHeight - fullgeo.size.height))
}
}
)
}
}
}
}
struct ScrollingView: View {
var body: some View {
VStack {
Text("View Top").font(.headline)
ForEach(0..<10) { _ in
Text("Content")
.frame(width: 200, height: 100)
.background(.white)
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.gray)
}
}
struct SecondView: View {
var body: some View {
Text("View Bottom").font(.headline)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(.orange)
}
}
Use this code:
import SwiftUI
struct PullToRefreshView: View
{
private static let minRefreshTimeInterval = TimeInterval(0.2)
private static let triggerHeight = CGFloat(100)
private static let indicatorHeight = CGFloat(100)
private static let fullHeight = triggerHeight + indicatorHeight
let backgroundColor: Color
let foregroundColor: Color
let isEnabled: Bool
let onRefresh: () -> Void
#State private var isRefreshIndicatorVisible = false
#State private var refreshStartTime: Date? = nil
init(bg: Color = .neutral0, fg: Color = .neutral90, isEnabled: Bool = true, onRefresh: #escaping () -> Void)
{
self.backgroundColor = bg
self.foregroundColor = fg
self.isEnabled = isEnabled
self.onRefresh = onRefresh
}
var body: some View
{
VStack(spacing: 0)
{
LazyVStack(spacing: 0)
{
Color.clear
.frame(height: Self.triggerHeight)
.onAppear
{
if isEnabled
{
withAnimation
{
isRefreshIndicatorVisible = true
}
refreshStartTime = Date()
}
}
.onDisappear
{
if isEnabled, isRefreshIndicatorVisible, let diff = refreshStartTime?.distance(to: Date()), diff > Self.minRefreshTimeInterval
{
onRefresh()
}
withAnimation
{
isRefreshIndicatorVisible = false
}
refreshStartTime = nil
}
}
.frame(height: Self.triggerHeight)
indicator
.frame(height: Self.indicatorHeight)
}
.background(backgroundColor)
.ignoresSafeArea(edges: .all)
.frame(height: Self.fullHeight)
.padding(.top, -Self.fullHeight)
}
private var indicator: some View
{
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: foregroundColor))
.opacity(isRefreshIndicatorVisible ? 1 : 0)
}
}
sample:
ScrollView{
VStack(spacing:0) {
//top of scrollView
PullToRefreshView{
//todo
}
}
}

Animate ViewBuilder content size change in SwiftUI

I am wondering how I can animate the content size of a ViewBuilder view. I have this:
struct CardView<Content>: View where Content: View {
private let content: Content
init(#ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack(spacing: 0) {
content
.padding(16)
}
.background(.white)
.cornerRadius(14)
.shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
}
}
I would like to animate any size changes to content, but I can't find a nice way of doing this. I found two ways that work:
Using animation(.linear) in CardView works, but is deprecated and discouraged since I have no value to attach the animation to.
Using withAnimation inside content when changing the content works, too, but I would like to encapsulate this behaviour in CardView. CardView is heavily reused and doing it in content is easy to forget and also not where this behaviour belongs in my opinion.
I also tried using GeometryReader but could not find a good way of doing it.
Here is an approach for you:
You may take look at this link as well:
How to replace deprecated .animation() in SwiftUI?
struct ContentView: View {
#State private var cardSize: CGSize = CGSize(width: 150, height: 200)
var body: some View {
VStack {
CardView(content: {
Color.red
.overlay(Image(systemName: "dollarsign.circle").resizable().scaledToFit().padding())
.onTapGesture {
cardSize = CGSize(width: cardSize.width + 50, height: cardSize.height + 50)
}
}, cardSize: cardSize)
}
}
}
struct CardView<Content>: View where Content: View {
let content: Content
let cardSize: CGSize
init(#ViewBuilder content: () -> Content, cardSize: CGSize) {
self.content = content()
self.cardSize = cardSize
}
var body: some View {
content
.frame(width: cardSize.width, height: cardSize.height)
.cornerRadius(14)
.padding(16)
.background(.white)
.shadow(color: .black.opacity(0.07), radius: 12, x: 0, y: 2)
.animation(.easeInOut, value: cardSize)
}
}
You might find this useful.
It uses a looping animation and a user gesture for add size and resting.
struct PilotTestPage: View {
#State private var cardSize = CGSize(width: 150, height: 200)
var body: some View {
return ZStack {
Color.yellow
CardView() {
Color.clear
.overlay(
Image(systemName: "dollarsign.circle")
.resizable()
.scaledToFit()
.padding()
)
}
.frame(
width: cardSize.width
,height: cardSize.height
)
.onTapGesture {
withAnimation {
cardSize = CGSize(
width: cardSize.width + 50
,height: cardSize.height + 50
)
}
}
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(.red)
.frame(
width: 200
,height: 44
)
.offset(y: -300)
.onTapGesture {
withAnimation {
cardSize = CGSize(
width: 150
,height: 200
)
}
}
}
.ignoresSafeArea()
}
struct CardView<Content>: View where Content: View {
let content: Content
init(
#ViewBuilder content: () -> Content
) {
self.content = content()
}
#State private var isAtStart = true
var body: some View {
ZStack {
content
.background(
RoundedRectangle(cornerRadius: 12)
.fill(.white)
.shadow(
color: .black.opacity(0.25)
,radius: 12
,x: 0
,y: 2
)
)
}
.scaleEffect(isAtStart ? 0.9 : 1.0)
.rotationEffect(.degrees(isAtStart ? -2 : 2))
.onAppear {
withAnimation(
.easeInOut(duration: 1)
.repeatForever()
) {
self.isAtStart.toggle()
}
}
}
}
}

how to make subview shadow bleeding out from parent view in SwiftUI?

As the title explained, I need make the shadow of subview bleeding out from parent bounds.
We can see the shadow is clipped by the container (parent view). How to ignore it?
ScrollView(axis, showsIndicators: false) {
HStack{
ForEach(d_keys.indices) { index -> DownloadOptionView in
let d_key = d_keys[index]
let d_info = asset.downloads[d_key]!
return DownloadOptionView(d_key: d_key, d_info: d_info)
}.padding(.vertical, 10) // ForEach
}
}.frame(minHeight:130)
// View structure for DownloadOptionView, modified, may not compile
struct DownloadOptionView: View {
let d_key: String
let d_info: DownloadInfo
// some #ObservedObject ...........
// some #State ...........
var body: some View {
return NavigationLink(destination: SceneKitCanvas(textureMap: textureMap), isActive: self.$present) {
Button(action: {
// Download / storage / animation
}) {
ZStack{
LinearGradient(gradient: Gradient(colors: [Neumorphism.background, Neumorphism.light]),
startPoint: .topLeading, endPoint: .bottomTrailing)
Color.green.scaleEffect(x: 1.0, y: CGFloat(scale), anchor: .bottom) // progress bar
Color.green.scaleEffect(x: 1.0, y: CGFloat(increased), anchor: lineAnchor)
VStack(alignment: .leading, spacing: 5) {
HStack(alignment: .center) {
Image(systemName: imageName).aspectRatio(contentMode: .fit)
Text(file_type).fontWeight(.light)
}.padding(.bottom, 5)
Text(d_key).font(.footnote).fontWeight(.light)
Text(size_final).font(.footnote).fontWeight(.light)
}.padding() // VStack
} // ZStack
}
.cornerRadius(20)
.shadow(color: Color.gray, radius: 3, x: 3, y: 3)
.shadow(color: Color.white, radius: 3, x: -3, y: -3)
}
}
}
I have done some guesswork about how you created DownloadOptionView but the answer can be used in your context as it's a general technique.
Where you are creating DownloadOptionView you need to add the .blendMode(.overlay) modifier:
struct DownloadOptionView: Shape {
func path(in rect: CGRect) -> Path {
return RoundedRectangle(cornerRadius: 8, style: .continuous).path(in: rect)
}
var body: some View {
RoundedRectangle(cornerSize: CGSize(width: 8, height: 8))
.frame(minWidth: 200, minHeight: 200)
.shadow(radius: 10)
.blendMode(.overlay) //Add here.
}
}

Rectangle in ZStack changing SwiftUI animation

I am trying to transition between two states with SwiftUI.
I have reduced this to a simple example
struct Test2View: View {
#State var isLoading: Bool = true
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(Color. secondary)
.shadow(radius: 4, x: 2, y: 5)
.frame(width: 300, height: 150, alignment: .center)
if isLoading {
Text("Loading")
.transition(.moveAndFade())
} else {
Text("Content")
.transition(.moveAndFade())
}
}.onTapGesture {
withAnimation {
self.isLoading.toggle()
}
}
}
}
extension AnyTransition {
static func moveAndFade(delay: TimeInterval = 0) -> AnyTransition {
let insertion = AnyTransition.offset(x: 0, y: 15)
.combined(with: .opacity)
let removal = AnyTransition.offset(x: 20, y: 20)
.combined(with: .opacity)
return .asymmetric(insertion: insertion, removal: removal)
}
}
This works the way I expected without the RoundedRectangle:
However, as soon as I add the RoundRectangle I lose the removal animation (unless I interrupt the animation, then you can see the animation I was expecting):
Any idea why the RoundedRectangle messes with the animation? I even tried to add .transition(.identity) without any success.
I can't tell the exact reason why the animation changes, but I have found the cause. It's something to do with the size of the frame.
Your version of Test2View's view body:
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(Color.white)
.shadow(radius: 4, x: 2, y: 5)
.frame(width: 300, height: 150, alignment: .center)
if isLoading {
Text("Loading")
.transition(.moveAndFade())
} else {
Text("Content")
.transition(.moveAndFade())
}
}
.border(Color.red)
.onTapGesture {
withAnimation {
self.isLoading.toggle()
}
}
Fixed version:
ZStack {
if isLoading {
Text("Loading")
.transition(.moveAndFade())
} else {
Text("Content")
.transition(.moveAndFade())
}
}
.background(
RoundedRectangle(cornerRadius: 10)
.fill(Color.white)
.shadow(radius: 4, x: 2, y: 5)
.frame(width: 300, height: 150, alignment: .center)
)
.border(Color.green)
.onTapGesture {
withAnimation {
self.isLoading.toggle()
}
}
Basically, I used .background instead of creating a VStack.
Comparison images:
The border color is there just to indicate the frame size. You can remove this!

Multiple ZStack controlling zIndex

Imagine a Grid (n x n) squares. Those squares are ZStacks. It contains an optinal piece (In this case a circle). If I offset that piece over another ZStack it gets hidden by the other ZStack.
What I'm trying to do is a chess game. Imagine the Circle() being a piece.
This was my initial attemp:
struct ContentView: View {
#State var circle1Offset: CGSize = .zero
var body: some View {
VStack {
ZStack {
Color.blue
Circle().fill(Color.black)
.frame(width: 50, height: 50)
.offset(circle1Offset)
.gesture(
DragGesture()
.onChanged { value in
self.circle1Offset = value.translation
}
)
}
.frame(width: 150, height: 150)
ZStack {
Color.red
}
.frame(width: 150, height: 150)
}
}
}
Also I tried to add an overlay() instead of using a ZStack. Not sure which is more precise for this case, but unluckily i can't add an "optional" overlay like so:
struct ContentView: View {
#State var circle1Offset: CGSize = .zero
var body: some View {
VStack {
Color.blue
.frame(width: 150, height: 150)
.overlay(
Circle().fill(Color.black)
.frame(width: 50, height: 50)
.offset(circle1Offset)
.gesture(
DragGesture()
.onChanged { value in
self.circle1Offset = value.translation
}
)
)
Color.red
.frame(width: 150, height: 150)
}
}
func tileHasPiece() -> Circle? {
return Circle() // This would consult my model
}
}
But as I said, I don't know how to use tileHasPiece() to add an overlay depending on this.
Just put all board static elements below, and all figure active elements above, as in below modified your code snapshot. In such case everthing will be in one coordinate space. (Of course calculation of coordinates for figures is out of this topic)...
struct FTContentView: View {
#State var circle1Offset: CGSize = .zero
var body: some View {
ZStack {
// put board below
VStack {
Color.blue
.frame(width: 150, height: 150)
Color.red
.frame(width: 150, height: 150)
}
// put figure above
Circle().fill(Color.black)
.frame(width: 50, height: 50)
.offset(circle1Offset)
.gesture(
DragGesture()
.onChanged { value in
self.circle1Offset = value.translation
}
)
} // board coordinate space
}
}

Resources