Adding button to a moving image SwiftUI - swiftui

I have successfully added a button to an image however when I click the button and the image moves via CGAffineTransform, the image moves but the button stays in the same place. When I click the space where the image used to be before the transform, the state toggle reacts. How do I get the button to move along with the image?
struct SliderView: View {
#State var partyType: Bool = false
var body: some View {
ZStack{
GeometryReader{ geo in
VStack(alignment: .leading){
Button(action: {
withAnimation (){
self.partyType.toggle();
}
}) {
Image("chevronPointer")
.clipped()
.modifier(SlideEffect(offset: CGSize(width: partyType ? geo.size.width - 70 : 0, height: 0.0)))
.animation(.smooth())
.padding(.horizontal)
}
Image("sliderLine")
.resizable()
.scaledToFit()
.frame(width: geo.size.width, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
}
}
.frame(height: 40)
}
}
}
struct SlideEffect: GeometryEffect {
var offset: CGSize
var animatableData: CGSize.AnimatableData {
get { CGSize.AnimatableData(offset.width, offset.height) }
set { offset = CGSize(width: newValue.first, height: newValue.second) }
}
public func effectValue(size: CGSize) -> ProjectionTransform {
return ProjectionTransform(CGAffineTransform(translationX: offset.width, y: offset.height))
}
}
struct SliderView_Previews: PreviewProvider {
static var previews: some View {
SliderView()
}
}

Just move the modifier outside the button.
Button(action: {
withAnimation(){
self.partyType.toggle()
}
}) {
Image(systemName: "chevron.up")
.clipped()
.animation(.default)
.padding(.horizontal)
}
.modifier(SlideEffect(offset: CGSize(width: partyType ? geo.size.width - 70 : 0, height: 0.0))) /// here!

Related

SwiftUI: Double picker wheels with system behavioral

I want to recreate system picker behavioral with two options in wheels with SwiftUI and faced ton of problem. Some of this I solved but some still unsolved. I have pop-ups with different views inside. One of the view it's a DatePicker with displayedComponents: .hourAndMinute. And other one is two Pickers inside HStack. My question is how to make Pickers make look like in system: without white spacing between?
struct MultyPicker: View {
#State var value = 1
#State var value2 = 1
var body: some View {
ZStack(alignment: .bottom) {
Color.black.opacity(0.5)
ZStack {
VStack {
Text("Header")
.font(.title3)
.fontWeight(.bold)
HStack(spacing: 0) {
Picker(selection: $value, label: Text("")) {
ForEach(1..<26) { number in
Text("\(number)")
.tag("\(number)")
}
}
.pickerStyle(WheelPickerStyle())
.compositingGroup()
.clipped(antialiased: true)
Picker(selection: $value2, label: Text("")) {
ForEach(25..<76) { number in
Text("\(number)")
.tag("\(number)")
}
}
.pickerStyle(WheelPickerStyle())
.compositingGroup()
.clipped(antialiased: true)
}
}
.padding()
.background(
RoundedRectangle(cornerRadius: 34)
.foregroundColor(.white)
)
}
.padding(.horizontal)
.padding(.bottom, 50)
}
.edgesIgnoringSafeArea([.top, .horizontal])
}
}
// This extension for correct touching area
extension UIPickerView {
open override var intrinsicContentSize: CGSize {
return CGSize(width: UIView.noIntrinsicMetric, height: super.intrinsicContentSize.height)
}
}
Want to achive looks like that with one grey line in selected value
//
// Test2.swift
// Test
//
// Created by Serdar Onur KARADAĞ on 26.08.2022.
//
import SwiftUI
struct Test2: View {
#State var choice1 = 0
#State var choice2 = 0
var body: some View {
ZStack {
Rectangle()
.fill(.gray.opacity(0.2))
.cornerRadius(30)
.frame(width: 350, height: 400)
Rectangle()
.fill(.white.opacity(1))
.cornerRadius(30)
.frame(width: 300, height: 350)
VStack {
Text("HEADER")
HStack(spacing: 0) {
Picker(selection: $choice1, label: Text("C1")) {
ForEach(0..<10) { n in
Text("\(n)").tag(n)
}
}
.pickerStyle(.wheel)
.frame(minWidth: 0)
.clipped()
Picker(selection: $choice2, label: Text("C1")) {
ForEach(0..<10) { n in
Text("\(n)").tag(n)
}
}
.pickerStyle(.wheel)
.frame(minWidth: 0)
.clipped()
}
}
}
}
}
struct Test2_Previews: PreviewProvider {
static var previews: some View {
Test2()
}
}
SwiftUI multi-component Picker basically consists of several individual Picker views arranged horizontally. Therefore, we start by creating an ordinary Picker view for our first component. I am using Xcode version 13.4.1(iOS 15.0).
import SwiftUI
struct ContentView: View {
#State var hourSelect = 0
#State var minuteSelect = 0
var hours = [Int](0..<24)
var minutes = [Int](0..<60)
var body: some View {
ZStack {
Color.black
.opacity(0.5)
.ignoresSafeArea()
.preferredColorScheme(.light)
Rectangle()
.fill(.white.opacity(1))
.cornerRadius(30)
.frame(width: 300, height: 350)
VStack {
Text("Header")
HStack(spacing: 0) {
Picker(selection: $hourSelect, label: Text("")) {
ForEach(0..<self.hours.count) { index in
Text("\(self.hours[index])").tag(index)
}
}
.pickerStyle(.wheel)
.frame(minWidth: 0)
.compositingGroup()
.clipped()
Picker(selection: $minuteSelect, label: Text("")) {
ForEach(0..<self.minutes.count) { index in
Text("\(self.minutes[index])").tag(index)
}
}
.pickerStyle(.wheel)
.frame(minWidth: 0)
.compositingGroup()
.clipped()
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Output :

How to draw a rectangle to a specific area of the screen based on user search input?

I am trying to make a program where the user enters a certain set of characters into the search bar (for example "AP1") and the program draws a rectangle on top of an image I have.
I will have a bunch of if statements testing what the user entered and giving the coordinates for where the rectangle will be drawn. I am just having trouble with the "scopes" and the ZStack and VStack for the image overlay not wanting to cooperate with how I have the if statement(s) set up. Here is my entire program:
This is my third day doing any type of iOS development
import SwiftUI
struct ContentView: View {
private var listOfBins = binList
#State var searchText = ""
var body: some View {
// MAP
VStack {
Image("map")
.resizable()
.scaledToFit()
.position(x: 195, y: 175)
.overlay(ImageOverlay(), alignment: .bottomTrailing)
Spacer()
}
NavigationView {
List {
ForEach(bins, id: \.self) { bin in
HStack {
Text(bin.capitalized)
.textCase(.uppercase)
Spacer()
Image(systemName: "figure.walk")
.foregroundColor(Color.blue)
}
.padding()
}
}
.searchable(text: $searchText)
.navigationTitle("Bins")
if (searchText.elementsEqual("AP1")) {
drawBox(width: 50, height: 50, x: 50, y: 50)
}
}
}
func drawBox(width: Int, height: Int, x: Int, y: Int) -> Rectangle{
struct ImageOverlay: View{
var body: some View {
ZStack {
Rectangle()
.fill(.green)
.frame(width: 100, height: 100)
.position(x: 200, y: 300)
}
}
}
}
// DISPLAY LIST OF BINS AND SEARCH BAR
var bins: [String] {
let upBins = listOfBins.map {$0.uppercased()}
return searchText == "" ? upBins : upBins.filter{
$0.contains(searchText.uppercased())
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
}
Could not run your provided code, so I replicated a temp view.
Is this something you wanted? (code is below the image)
struct SSContentView: View {
#State var searchText = ""
var images = ["Swift", "Ww", "Luffy"]
var body: some View {
NavigationView {
List {
TextField("Search Here", text: $searchText)
ForEach(0...5, id: \.self) { _ in
ForEach(images, id: \.self) { image in
ZStack {
Image(image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50)
.overlay {
if searchText == image {
OverlayImage
}
}
}
}
}
}
.navigationTitle("My Pictures")
}
}
var OverlayImage: some View {
ZStack {
Rectangle()
.fill(.clear)
.frame(width: 60, height: 60)
.border(.green)
}
}
}

How do I create this effect using SwiftUI

See this gif
What is happening on this gif is: the finger touches the white area on any point on the right part and drags to the left. As the finger drags, these 3 buttons zoom in and appear.
Assuming the buttons zoom from scale = 0 to scale = 1, Does not matter if I release the finger when the scale is at any value bigger than 0. The buttons will zoom to scale 1 automatically.
NOTE: The animation is slow because I am sliding the finger slowly, but the animation follows the finger drag. If I drag left, buttons zoom in, if I drag right, buttons zoom out.
How do I do that with SwiftUI.
I have this code so far for the whole thing.
struct FileManagerPanelListItem: View {
var body: some View {
ZStack{
VStack {
ZStack {
Image("image")
.resizable()
.frame(width: 190, height: 190, alignment: .center)
HStack(alignment:.center){
FileManagerPanelButton("share", Color.white, Color.gray, {})
FileManagerPanelButton("duplicate", Color.white, Color.gray, {})
FileManagerPanelButton("delete", Color.white, Color.red, {})
}
.frame(maxWidth:.infinity)
}
Text("name")
.frame(width:170)
.background(Color.yellow)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
Text("notes")
.frame(width:170)
.background(Color.blue)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
}
}
.frame(maxWidth:240, maxHeight: 240)
}
}
struct FileManagerPanelListItem_Previews: PreviewProvider {
static var previews: some View {
FileManagerPanelListItem()
.previewDevice(PreviewDevice(rawValue: "iPhone 12 Pro Mini"))
}
}
struct FileManagerPanelButton: View {
typealias runOnSelectHandler = ()->Void
private var runOnSelect:runOnSelectHandler?
private var label:String
private var fgColor:Color
private var bgColor:Color
init(_ label: String,
_ fgColor:Color,
_ bgColor:Color,
_ runOnSelect: runOnSelectHandler? ) {
self.label = label
self.fgColor = fgColor
self.bgColor = bgColor
self.runOnSelect = runOnSelect
}
var body: some View {
Button(action: {
runOnSelect?()
}, label: {
Text(label)
.avenir(.ROMAN, size: 16)
.foregroundColor(fgColor)
.frame(height:60)
.frame(maxWidth:.infinity)
})
.frame(maxWidth:.infinity)
.background(bgColor)
.cornerRadius(10)
}
}
any ideas?
Something like this should work:
// view cannot be scaled to zero, so we take some small value near
private let minScale: CGFloat = 0.001
// relative distance from right side of view to open and from left side to close
private let effectiveDragSidePart: CGFloat = 0.1
struct FileManagerPanelListItem: View {
#State
var scale: CGFloat = minScale
#State
var opened = false
var body: some View {
ZStack{
VStack {
ZStack {
Image("profile")
.resizable()
.frame(width: viewWidth, height: 190, alignment: .center)
.gesture(
DragGesture()
.onChanged { value in
guard shouldProcessStartLocation(value.startLocation) else { return }
scale = translationToScale(value.translation)
}
.onEnded { value in
guard shouldProcessStartLocation(value.startLocation) else { return }
withAnimation(.spring()) {
if shouldRestore(value.predictedEndTranslation) {
scale = opened ? 1 : minScale
} else {
scale = opened ? minScale : 1
opened.toggle()
}
}
}
)
HStack(alignment:.center){
FileManagerPanelButton("share", Color.white, Color.gray, {})
.scaleEffect(scale, anchor: .center)
FileManagerPanelButton("duplicate", Color.white, Color.gray, {})
.scaleEffect(scale, anchor: .center)
FileManagerPanelButton("delete", Color.white, Color.red, {})
.scaleEffect(scale, anchor: .center)
}
.frame(maxWidth:.infinity)
}
}
}
.frame(maxWidth:240, maxHeight: 240)
}
private let viewWidth: CGFloat = 190
private func shouldRestore(_ predictedEndTranslation: CGSize) -> Bool {
if opened {
return translationToScale(predictedEndTranslation) == 1
} else {
return translationToScale(predictedEndTranslation) == minScale
}
}
private func shouldProcessStartLocation(_ startLocation: CGPoint) -> Bool {
if opened {
return startLocation.x < viewWidth * effectiveDragSidePart
} else {
return startLocation.x > viewWidth * (1 - effectiveDragSidePart)
}
}
private func translationToScale(_ translation: CGSize) -> CGFloat {
if opened {
return max(0.001, min(1, 1 - translation.width / viewWidth))
} else {
return max(0.001, -translation.width / viewWidth)
}
}
}
You can setup .spring() if the default one is not springy enough

Animated shutter button like in the camera app in SwiftUI

How can you build something like the animated shutter button in the camera app in SwiftUI?
// SwiftUIPlayground
// https://github.com/ralfebert/SwiftUIPlayground/
import SwiftUI
struct CameraButtonView: View {
#State var recording = false
var action: ((_ recording: Bool) -> Void)?
var body: some View {
ZStack {
Circle()
.stroke(lineWidth: 6)
.foregroundColor(.white)
.frame(width: 65, height: 65)
RoundedRectangle(cornerRadius: recording ? 8 : self.innerCircleWidth / 2)
.foregroundColor(.white)
.frame(width: self.innerCircleWidth, height: self.innerCircleWidth)
}
.animation(.linear(duration: 0.2))
.padding(20)
.onTapGesture {
withAnimation {
self.recording.toggle()
self.action?(self.recording)
}
}
}
var innerCircleWidth: CGFloat {
self.recording ? 32 : 55
}
}
struct CameraButtonView_Previews: PreviewProvider {
static var previews: some View {
Group {
CameraButtonView(recording: false)
.previewLayout(PreviewLayout.sizeThatFits)
.previewDisplayName("not recording")
.background(Color.gray)
CameraButtonView(recording: true)
.previewLayout(PreviewLayout.sizeThatFits)
.previewDisplayName("recording")
.background(Color.gray)
ZStack {
Image("turtlerock")
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
Spacer()
CameraButtonView()
}
}
}
}
}

swiftUI Button with width:0 nonetheless active

I set the width of a SwiftUI Button to 0 to "deactivate" it.
If the with of the button is set to 0, the button disappears as expected, but clicking in the left edge of the yellow Stack activates the Button.
Why does this happen?
How can I avoid it?
struct ContentView: View {
#State var zeroWidth = false
var body: some View {
VStack{
ButtonLine( leftButtons: [ButtonAttr( label: "LB1",
action: {print("LB1")},
iconSystemName : "person"
)],
zeroWidth: zeroWidth
)
Button("Toggle width \(zeroWidth ? "On" : "Off" ) "){ self.zeroWidth.toggle() }
}
}
}
struct ButtonLine: View {
let leftButtons : [ButtonAttr]
let zeroWidth : Bool
var body: some View {
HStack(spacing: 0) {
ForEach(leftButtons.indices, id: \.self)
{ i in
HStack(spacing: 0.0)
{
Button(action: { self.leftButtons[i].action() }) {
ButtonLabel( singleline: false,
buttonAttr: self.leftButtons[i]
)
.padding(0)
//.background(Color.green) // not visible
}
.buttonStyle(BorderlessButtonStyle())
.frame( width: self.zeroWidth ? 0 : 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)
}
// .background(Color.blue) // not visible
}
// .background(Color.blue) // not visible
Spacer()
Text("CONTENT")
.background(Color.green)
.onTapGesture {
print("Content tapped")
}
Spacer()
}
.background(Color.yellow)
.onTapGesture {
print("HS tapped")
}
}
}
struct ButtonLabel: View {
var singleline : Bool
var buttonAttr : ButtonAttr
var body: some View {
VStack (spacing: 0.0) {
Image(systemName: buttonAttr.iconSystemName).frame(height: singleline ? 0 : 20).clipped()
.padding(0)
.background(Color.blue)
Text(buttonAttr.label)
.padding(0)
.background(Color.blue)
}
.padding(0)
.background(Color.red)
}
}
struct ButtonAttr
{ let label : String
let action: ()-> Void
let iconSystemName : String
}
Instead of tricky "deactivate", just use real remove, like below
HStack(spacing: 0.0)
{
if !self.zeroWidth {
Button(action: { self.leftButtons[i].action() }) {
ButtonLabel( singleline: false,
buttonAttr: self.leftButtons[i]
)
.padding(0)
//.background(Color.green) // not visible
}
.buttonStyle(BorderlessButtonStyle())
.frame(width: 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)
}
}.frame(height: 50) // to keep height persistent
there is very simple explanation.
try next snippet
struct ContentView: View {
var body: some View {
Text("Hello").padding().border(Color.yellow).fixedSize().frame(width: 0)
}
}
Why?
.frame(..)
is defined as a function of View, which return another View, as any kind of View modifier. The resulting View has .zero sized frame, as expected.
It is really true? Let's check it!
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle()
.fill(Color.orange)
.frame(width: 100, height: 100)
Text("Hello")
.padding()
.border(Color.black)
.fixedSize()
.frame(width: 0, height: 0)
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.blendMode(.exclusion)
}
}
}
Just add .clipped modifier to your Text View
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle()
.fill(Color.orange)
.frame(width: 100, height: 100)
Text("Hello")
.padding()
.border(Color.black)
.fixedSize()
.frame(width: 0, height: 0)
.clipped()
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.blendMode(.exclusion)
}
}
}
and the Text "disappears" ...
It disappears from the screen, but not from View hierarchy!. Change the code again
struct ContentView: View {
var body: some View {
HStack(spacing: 0) {
Rectangle()
.fill(Color.orange)
.frame(width: 100, height: 100)
Text("Hello")
.padding()
.border(Color.black)
.fixedSize().onTapGesture {
print("tap")
}
.frame(width: 0, height: 0)
.clipped()
Rectangle()
.fill(Color.green)
.frame(width: 100, height: 100)
.blendMode(.exclusion)
}
}
}
and you see, that there is still some "invisible" area sensitive on tap gesture
You can disable you Button by adding a .disabled(self.zeroWidth)
Button(action: { self.leftButtons[i].action() }) {
ButtonLabel( singleline: false,
buttonAttr: self.leftButtons[i]
)
.padding(0)
//.background(Color.green) // not visible
}
.disabled(self.zeroWidth)
.buttonStyle(BorderlessButtonStyle())
.frame( width: self.zeroWidth ? 0 : 100, height: 50)
.background(Color.green)
.clipped()
.foregroundColor(Color.white)
.padding(0)
You can debug the view hierarchy by clicking that icon in xcode: