SwiftUI Charts LineMark symbol touch event - ios

I have a chart:
Create by code:
Chart {
ForEach(viewModel.historyChar) { chartItem in
AreaMark(
x: .value("Date", chartItem.date),
y: .value("Amount", chartItem.amount)
)
LineMark(
x: .value("Date", chartItem.date),
y: .value("Amount", chartItem.amount)
)
.symbol() {
Button(action: {
print("chartItem: ", chartItem)
}, label: {
Circle()
.fill(color)
.frame (width: 15)
.opacity(0.6)
})
}
}
I need to touch LineMark symbol and get chartItem. I tried to use Button inside .symbol(), but it don't works
Hot to add touch event to LineMark symbol?

Try this simple approach, works well for me. It uses the chartOverlay and a function tapSymbol to calculate which symbol was tapped. The Circle symbol change to green when tapped.
import Foundation
import SwiftUI
import Charts
struct Measurement: Identifiable {
var id: String
var date: Double
var amount: Double
static let dx: CGFloat = 0.5 // <--- adjust as required
static let dy: CGFloat = 2.0 // <--- adjust as required
func isAround(x: CGFloat, y: CGFloat) -> Bool {
return date <= (x + Measurement.dx) && date >= (x - Measurement.dx) && amount <= (y + Measurement.dy) && amount >= (y - Measurement.dy)
}
}
struct ContentView: View {
let measurement = [
Measurement(id: "1", date: 2.0, amount: 11.0),
Measurement(id: "2", date: 4.0, amount: 22.0),
Measurement(id: "3", date: 6.0, amount: 38.0),
Measurement(id: "4", date: 8.0, amount: 45.0),
Measurement(id: "5", date: 10.0, amount: 30.0),
Measurement(id: "6", date: 12.0, amount: 57.0),
Measurement(id: "7", date: 14.0, amount: 26.0)
]
#State var selected = "0"
func tapSymbol(at location: CGPoint, proxy: ChartProxy, geometry: GeometryProxy) {
let xPos = location.x - geometry[proxy.plotAreaFrame].origin.x
let yPos = location.y - geometry[proxy.plotAreaFrame].origin.y
if let pos: (Double, Double) = proxy.value(at: CGPoint(x: xPos, y: yPos)) {
let results = measurement.filter{ $0.isAround(x: pos.0, y: pos.1) }
if let firstId = results.first?.id {
selected = firstId
}
}
}
var body: some View {
Chart {
ForEach(measurement) { chartItem in
AreaMark(
x: .value("Date", chartItem.date),
y: .value("Amount", chartItem.amount)
)
LineMark(
x: .value("Date", chartItem.date),
y: .value("Amount", chartItem.amount)
)
.symbol {
Circle().fill(selected == chartItem.id ? Color.green : Color.red).opacity(0.6).frame(width: 30)
}
}
}
.chartOverlay { proxy in
GeometryReader { geometry in
ZStack(alignment: .top) {
Rectangle().fill(.clear).contentShape(Rectangle())
.onTapGesture { location in
tapSymbol(at: location, proxy: proxy, geometry: geometry)
}
}
}
}
}
}

Related

SwiftUI Charts tapping Bar Chart

I have used the new SwiftUI Charts to make a simple bar chart, with month names on the x axis and cost on the y axis. I have added a .chartOverlay view modifier which tracks where I tap on the chart, and shows a BarMark with a text of the cost of the bar tapped.
This was very straightforward to implement, but when starting tapping on the bars, I found that tapping on the first and second bar produced the wanted result, but starting with the third bar, the bar tapped and the second bar was swapped, but the BarMark text showed was correct.
I have used a few hours on this problem, but can't find out what is wrong here.
The complete code is shown here, if anyone can see what is wrong:
import SwiftUI
import Charts
final class ViewModel: ObservableObject {
let months = ["Nov", "Dec", "Jan", "Feb", "Mar", "Apr"]
let costs = [20.0, 40.0, 80.0, 60.0, 75.0, 30.0]
#Published var forecast: [Forecast] = []
#Published var selectedMonth: String?
#Published var selectedCost: Double?
init() {
forecast = months.indices.map { .init(id: UUID(), month: months[$0], cost: costs[$0]) }
}
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
Chart(viewModel.forecast) { data in
BarMark(x: .value("Month", data.month), y: .value("Kr", data.cost))
.foregroundStyle(Color.blue)
if let selectedMonth = viewModel.selectedMonth, let selectedCost = viewModel.selectedCost {
RuleMark(x: .value("Selected month", selectedMonth))
.annotation(position: .top, alignment: .top) {
VStack {
Text(estimated(for: selectedMonth, and: selectedCost))
}
}
}
}
.chartYAxis {
AxisMarks(position: .leading)
}
.chartOverlay { proxy in
GeometryReader { geometry in
ZStack(alignment: .top) {
Rectangle().fill(.clear).contentShape(Rectangle())
.onTapGesture { location in
updateSelectedMonth(at: location, proxy: proxy, geometry: geometry)
}
}
}
}
}
func updateSelectedMonth(at location: CGPoint, proxy: ChartProxy, geometry: GeometryProxy) {
let xPosition = location.x - geometry[proxy.plotAreaFrame].origin.x
guard let month: String = proxy.value(atX: xPosition) else {
return
}
viewModel.selectedMonth = month
viewModel.selectedCost = viewModel.forecast.first(where: { $0.month == month })?.cost
}
func estimated(for month: String, and cost: Double) -> String {
let estimatedString = "estimated: \(cost)"
return estimatedString
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.padding()
}
}
struct Forecast: Identifiable {
let id: UUID
let month: String
let cost: Double
}
try this approach, using a ForEach loop and having the RuleMark after it:
var body: some View {
Chart { // <-- here
ForEach(viewModel.forecast) { data in // <-- here
BarMark(x: .value("Month", data.month), y: .value("Kr", data.cost))
.foregroundStyle(Color.blue)
} // <-- here
if let selectedMonth = viewModel.selectedMonth,
let selectedCost = viewModel.selectedCost {
RuleMark(x: .value("Selected month", selectedMonth))
.annotation(position: .top, alignment: .top) {
VStack {
Text(estimated(for: selectedMonth, and: selectedCost))
}
}
}
}
.chartYScale(domain: 0...100) // <-- here
.chartYAxis {
AxisMarks(position: .leading)
}
.chartOverlay { proxy in
GeometryReader { geometry in
ZStack(alignment: .top) {
Rectangle().fill(.clear).contentShape(Rectangle())
.onTapGesture { location in
updateSelectedMonth(at: location, proxy: proxy, geometry: geometry)
}
}
}
}
}

animation lets view jump

I created a list of cells which are collapsable. It works quite well, but the title headed jumps around while being animated, as you can see in this small video. It should stay put in place. What am I doing wrong? Thanks
A compilable code can be found here: https://gitlab.com/vikingosegundo/brighter-hue
// LightCell.swift
let rowHeight = 36.0
let collapsedCellHeight = 80.0
let uncollapsedCellHeight = 180.0
let material:Material = .thickMaterial
struct LightCell: View {
init(light: Light, rootHandler: #escaping (Message) -> ()) {
self.rootHandler = rootHandler
self.light = light
self.isOn = light.isOn
self.hue = light.hue
self.sat = light.saturation
self.bri = light.brightness
self.ct = Double(-light.ct)
self.interface = light.display
self.selectedMode = light.modes.contains(.hsb) ? .hsb : .ct
self.values = light.modes.contains(.hsb) ? .hsb(light.hue, light.saturation, light.brightness) : .ct(light.ct, light.brightness)
}
var body: some View {
cellLayout
.frame( height: isCollapsed ? collapsedCellHeight : uncollapsedCellHeight)
.modifier(Height(isCollapsed ? collapsedCellHeight : uncollapsedCellHeight))
.swipeActions(edge: .leading ) { toggleColorMode }
.swipeActions(edge: .trailing) { toggleInterface }
.listRowBackground( bgColor )
.tint(hsbColor(opacity: 0.5))
.padding(.top, 15)
.padding(.bottom, 15)
.buttonStyle(PlainButtonStyle())
.onChange(of:light.ct ) { ct = Double(-$0); values = .ct (-$0, light.brightness) }
.onChange(of:light.hue ) { hue = $0; values = .hsb(-$0, light.saturation, light.brightness) }
.onChange(of:light.saturation) { sat = $0; values = .hsb(light.hue, $0, light.brightness) }
.onChange(of:light.brightness) { bri = $0; values = .hsb(light.hue, light.saturation, $0 ) }
}
private let rootHandler: (Message) -> ()
private let light: Light
#EnvironmentObject
private var viewState: ViewState
#State private var values: Values
#State private var isOn : Bool
#State private var hue : Double
#State private var sat : Double
#State private var bri : Double
#State private var ct : Double
#State private var interface : Interface
#State private var startPoint : CGPoint?
#State private var currentPoint : CGPoint?
#State private var isCollapsed : Bool = true
#State private var selectedMode : Light.Mode
}
private
extension LightCell {
private
var cellLayout: some View {
VStack {
cellheader;
if light.modes.contains(.hsb) || light.modes.contains(.ct) {
switch (selectedMode, light.display) {
case (.ct, .slider):
Spacer() .isCollapsed(isCollapsed)
ctSliderRow.isCollapsed(isCollapsed)
case (.hsb, .slider):
Spacer() .isCollapsed(isCollapsed)
hueSliderRow.isCollapsed(isCollapsed);
satSliderRow.isCollapsed(isCollapsed)
case (_, .scrubbing):
scrubbingView.isCollapsed(isCollapsed)
}
}
briSliderRow
}
}
private
var toggleColorMode: some View {
HStack {
if !isCollapsed{
let indexedModes = light
.modes
.filter { $0 != selectedMode }
.enumerated()
.map { (idx: $0.offset, mode: $0.element) }
ForEach(indexedModes, id: \.idx) { i in
Button { selectedMode = i.mode } label: { image(for: i.mode) }
}}
}
}
private
var toggleInterface: some View {
HStack {
if !isCollapsed{
Button {
rootHandler(.lighting(.change(.display(light.display == .slider ? .scrubbing : .slider), on:light)))
} label: { Label("", systemImage:
light.display == .slider
? "slider.horizontal.below.rectangle"
: "slider.horizontal.3")
}}
}
}
private
var cellheader: some View {
HStack {
Button {
withAnimation { isCollapsed.toggle() }
} label: {
Image(systemName:"chevron.up")
.foregroundColor(hsbColor(opacity:0.75))
.contentShape(Rectangle())
.rotationEffect( .radians(isCollapsed ? .pi : 0.0))
}
titleLabel
.multilineTextAlignment(.leading)
Spacer()
toogle
}
.rowStyle(height:rowHeight)
}
private
var titleLabel: some View {
Text("\(light.name)")
.bold()
.dynamicTypeSize(.xLarge)
.fixedSize()
}
private
var toogle: some View {
Toggle("", isOn: $isOn)
.onChange(of: isOn) { _ in
switch (light.isOn, isOn) {
case (true, false): rootHandler(.lighting(.turn(light,.off)))
case (false, true): rootHandler(.lighting(.turn(light,.on )))
case ( _ , _ ): ()
}
}
}
private
var scrubbingKnob: some View {
HStack {
Spacer()
Circle()
.fill(
LinearGradient(gradient: Gradient(colors: [hsbColor(saturation:1.0, opacity: 0.5),
hsbColor(saturation:1.0, opacity: 0.5),
hsbColor(saturation:0.1, opacity: 0.5),
hsbColor(saturation:0.1, opacity: 0.5)]),
startPoint:.top, endPoint:.bottom)
)}
.frame(minWidth: 20, maxWidth: 20, minHeight:20)
.offset(x:(currentPoint?.x ?? 0) - (startPoint?.x ?? 0), y:(currentPoint?.y ?? 0) - (startPoint?.y ?? 0))
.gesture(
DragGesture(
minimumDistance: 5,
coordinateSpace: .global
).onChanged{ n in
if let startpoint = startPoint {
switch selectedMode {
case .hsb:setHueAndSaturationFor(
distanceX: ( n.location.x ) - (startpoint.x),
distanceY: ( n.location.y ) - (startpoint.y))
case .ct: setCTfor(
distanceX: ( n.location.x ) - (startpoint.x),
distanceY: ( n.location.y ) - (startpoint.y))
}
}
switch startPoint == nil {
case true : startPoint = n.location
case false: currentPoint = n.location
}}
.onEnded { _ in startPoint = nil; currentPoint = nil } ).padding(.bottom, 20)
}
private
var scrubbingView: some View {
ZStack {
VStack {
Spacer()
scrubbingKnob
Spacer()
}
}
.contentShape(Rectangle())
}
private
var ctSliderRow: some View {
HStack {
Button {
rootHandler( .lighting(.increase(.colortemp,by:10.pt,on:light)) )
} label: {
Image(systemName:"thermometer.snowflake")
.foregroundStyle(ctColor(ct:(Double(light.ct) * 1.2)))
.accessibility(label:Text("increase color temperature"))
}.frame(width: 24)
Slider(value: $ct, in: (-500)...(-153)) { _ in rootHandler( .lighting(.apply(.values(.ct(Int(-ct),bri)),on:light))) }
.accessibility(label:Text("change color temperature"))
Button {
rootHandler( .lighting(.decrease(.colortemp,by:10.pt,on:light)) )
} label: {
Image(systemName:"thermometer.sun")
.foregroundStyle(ctColor(ct:(Double(light.ct) * 0.8)))
.accessibility(label:Text("decrease color temperature"))
}.frame(width: 24)
}
.rowStyle(height: (selectedMode == .ct && !isCollapsed) ? rowHeight : 0)
.opacity((selectedMode == .ct && !isCollapsed) ? 1.0 : 0.0)
}
private
var briSliderRow: some View {
slider(
bind: $bri,
leading:
.init(action: {
rootHandler( .lighting(.decrease(.brightness,by:10.pt,on:light)) ) },
image: .system("sun.min"),
color: hsbColor(brightness: min(max(light.brightness - 0.05, 0.1), 1.0)),
accessibility: "decrease brightness"),
slider:
.init(action: { rootHandler( .lighting(.apply(.values(.bri(bri)),on:light))) },
accessibility:"change brightness"),
trailing:
.init(action: {
rootHandler( .lighting(.increase(.brightness,by:10.pt,on:light)) ) },
image: .system("sun.max.fill"),
color: hsbColor(brightness: min(max(light.brightness + 0.05, 0.1), 1.0)),
accessibility: "increase brightness")
).rowStyle(height: rowHeight)
}
private
var hueSliderRow: some View {
slider(
bind: $hue,
leading:
.init(action: { rootHandler( .lighting(.decrease(.hue,by:10.pt,on:light)) ) },
image: .system("paintpalette"),
color: hsbColor(hue: min(max(light.hue - 0.05, 0.1), 1.0)),
accessibility: "decrease saturation"),
slider:
.init(action: { rootHandler( .lighting(.apply(.values(.hsb(hue, sat, bri)),on:light))) },
accessibility: "change saturation"),
trailing:
.init(
action: { rootHandler( .lighting(.increase(.hue,by:10.pt, on:light)) ) },
image: .system("paintpalette"),
color: hsbColor(hue: min(max(light.hue + 0.05, 0.1), 1.0)),
accessibility: "increase saturation")
)
.rowStyle(height: (selectedMode == .hsb && !isCollapsed) ? rowHeight : 0)
.opacity((selectedMode == .hsb && !isCollapsed) ? 1.0 : 0.0)
}
private
var satSliderRow: some View {
slider(
bind: $sat,
leading:
.init(action: { rootHandler( .lighting(.decrease(.saturation,by:10.pt, on:light)) ) },
image: .system("circle.lefthalf.fill"),
color: hsbColor(saturation: min(max(light.saturation - 0.25, 0.1), 1.0)),
accessibility: "decrease saturation"),
slider:
.init(action: { rootHandler( .lighting(.apply(.values( .hsb(hue, sat, bri)),on:light))) },
accessibility: "change saturation"),
trailing:
.init(
action: { rootHandler( .lighting(.increase(.saturation,by:10.pt, on:light)) ) },
image: .system("circle.righthalf.fill"),
color: hsbColor(saturation: min(max(light.saturation + 0.25, 0.1), 1.0)),
accessibility: "increase saturation")
)
.rowStyle(height: (selectedMode == .hsb && !isCollapsed) ? rowHeight : 0)
.opacity((selectedMode == .hsb && !isCollapsed) ? 1.0 : 0.0)
}
private
func imageForSlider(conf: SliderButtonConf.ButtonImage) -> Image { switch conf { case .system(let name): return Image(systemName: name) } }
private
func slider(
bind: Binding<Double>,
leading:SliderButtonConf, slider:SliderConf, trailing:SliderButtonConf) -> some View {
HStack {
Button {
leading.action()
} label: {
imageForSlider(conf: leading.image)
.foregroundStyle(leading.color)
.accessibility(label:Text(leading.accessibility))
}.frame(width: 24)
Slider(value: bind){ _ in
slider.action()
}.accessibility(label:Text(slider.accessibility))
Button {
trailing.action()
} label: {
imageForSlider(conf: trailing.image)
.foregroundStyle(trailing.color)
.accessibility(label:Text(trailing.accessibility))
}.frame(width: 24)
}
}
}
fileprivate
struct SliderButtonConf {
let action: () -> ()
let image: ButtonImage
let color: Color
let accessibility:String
enum ButtonImage {
case system(String)
}
}
fileprivate
struct SliderConf {
let action: () -> ()
let accessibility:String
}
private
extension LightCell {
func selected(_ mode: Light.Mode) { selectedMode = mode }
}
fileprivate
extension View {
#ViewBuilder func rowStyle(height:Double) -> some View {
self.frame(height:height)
.padding(.trailing, 15)
.padding(.leading, 15)
.background(material, in: RoundedRectangle(cornerRadius: 5))
}
#ViewBuilder func isCollapsed(_ isCollapsed:Bool) -> some View {
self.opacity(isCollapsed ? 0 : 1).frame(maxHeight: isCollapsed ? 0 : .infinity)
}
}
fileprivate
struct Height: AnimatableModifier {
init(_ height: Double) {
self.height = height
}
private var height = 0.0
var animatableData: Double {
get { height }
set { height = newValue }
}
func body(content: Content) -> some View {
content.frame(height: height)
}
}

ERROR: IOSurfaceCreate 'RGBA' failed when calling requestImage from PHCachingImageManager

I am using PHCachingImageManager to get thumbnail images for all images in the iOS photo library which are then rendered using a SwiftUI List. It seems to work for a small number of images (e.g. the six that load with the simulator), but given 1000s of images I'm incurring this error many times.
IIO_CreateIOSurfaceWithFormatAndBuffer:594: *** ERROR: IOSurfaceCreate 'RGBA' failed - clientAddress: 0x14d6a0000 allocSize: 0x00072000 size: 256 x 456 rb: 1024 [0x00000400] bpp: 4
What does this mean and what is the root cause? Does access to PHCachingImageManager need to be throttled down?
Below is a class similar to the one in my app that reproduces the issue on my iPhone SE2.
import Foundation
import SwiftUI
import UIKit
import Photos
let thumbnailSize = CGSize(width: 90, height: 90)
struct PhotoSelectView: View {
class ImageRowManager {
let thumbnailImageRequestOptions: PHImageRequestOptions
let cachingImageManager = PHCachingImageManager()
var rows: [SelectableImageRow] = []
init() {
let options = PHImageRequestOptions()
options.isSynchronous = true
options.resizeMode = .fast
options.deliveryMode = .highQualityFormat
options.isNetworkAccessAllowed = false
self.thumbnailImageRequestOptions = options
}
func add(row: SelectableImageRow) {
self.rows.append(row)
}
}
struct SelectableImageRow: Hashable {
var rowIndex: Int
var images: [SelectableImage]
func hash(into hasher: inout Hasher) {
hasher.combine(self.rowIndex)
}
}
class SelectableImage: Hashable, ObservableObject {
#Published var image: UIImage? = nil
let id: String
private let asset: PHAsset
private let imageRowManager: ImageRowManager
init(asset: PHAsset, imageRowManager: ImageRowManager) {
self.id = asset.localIdentifier
self.asset = asset
self.imageRowManager = imageRowManager
self.loadImage()
}
func loadImage() {
DispatchQueue.global(qos: .background).async {
self.imageRowManager.cachingImageManager.requestImage(for: self.asset, targetSize: CGSize(width: 150, height: 150), contentMode: .aspectFill, options: self.imageRowManager.thumbnailImageRequestOptions) { (image, _) in
RunLoop.main.perform {
self.image = image
}
}
}
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.id)
}
static func ==(lhs: SelectableImage, rhs: SelectableImage) -> Bool {
return lhs.id == rhs.id
}
}
let imageRowManager = ImageRowManager()
#State var selected = Set<SelectableImage>()
#State var grid: [SelectableImageRow] = []
var body: some View {
VStack {
VStack {
if !self.grid.isEmpty {
HStack {
Text("Pick images")
Spacer()
}
.padding(.leading)
.padding(.top)
ImagesScrollView(grid: self.$grid, selected: self.$selected)
Button(action: {
self.handleSelectButton()
}) {
Text("Select")
.foregroundColor(Color.black.opacity((self.selected.count != 0) ? 1 : 0.5))
.padding(.vertical,10)
.frame(width: UIScreen.main.bounds.width / 2)
.overlay(
Capsule(style: .continuous)
.stroke(Color.black.opacity((self.selected.count != 0) ? 1 : 0.5), style: StrokeStyle(lineWidth: 5))
)
}
.background(Color.white)
.padding(.bottom)
.disabled((self.selected.count != 0) ? false : true)
}
}
.frame(width: UIScreen.main.bounds.width - CGFloat(horizontalPadding), height: UIScreen.main.bounds.height / 1.5)
.background(Color.white)
.cornerRadius(12)
}
.background(Color.black.opacity(0.1)).edgesIgnoringSafeArea(.all)
.onAppear {
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
self.getAllImages()
} else {
print("Cannot access photo library")
}
}
}
}
private func handleSelectButton() {
print("selected images", self.selected)
}
private func getAllImages() {
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let req = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var rowIndex = 0
for i in stride(from: 0, to: req.count, by: gridItemWidth) {
var iteration : [SelectableImage] = []
for j in i..<i+gridItemWidth {
if j < req.count{
iteration.append(SelectableImage(asset: req[j], imageRowManager: self.imageRowManager))
}
}
let row = SelectableImageRow(rowIndex: rowIndex, images: iteration)
imageRowManager.add(row: row)
rowIndex += 1
}
self.grid = imageRowManager.rows
}
// Subviews
struct ImagesScrollView: View {
#Binding var grid: [SelectableImageRow]
#Binding var selected: Set<SelectableImage>
var body: some View {
List(self.grid, id: \.self) { row in
SelectableImageRowView(row: row, selected: self.$selected)
}
}
}
struct SelectableImageRowView: View {
var row: SelectableImageRow
#Binding var selected: Set<SelectableImage>
var body: some View {
HStack(spacing: 2) {
ForEach(row.images, id: \.self) { img in
SelectableImageCard(data: img, selected: self.$selected)
}
}
}
}
struct SelectableImageCard: View {
#ObservedObject var data: SelectableImage
#Binding var selected: Set<SelectableImage>
var body: some View {
ZStack {
Image(uiImage: self.image()).resizable()
if self.selected.contains(self.data) {
Image(systemName: "checkmark")
.resizable()
.padding(7)
.foregroundColor(.white)
.background(Color.blue)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 1))
.frame(width: 30, height: 30, alignment: .topTrailing)
.offset(x: 30, y: -28)
}
}
.frame(width: thumbnailSize.width, height: thumbnailSize.height)
.onTapGesture {
if !self.selected.contains(self.data) {
self.selected.insert(self.data)
} else{
self.selected.remove(self.data)
}
}
}
private func image() -> some UIImage {
if let image = self.data.image {
return image
} else {
return UIImage(systemName: "heart.fill")!
}
}
}
}

SwiftUI: Gesture doesn't work , it won't move

I have an array of User and my goal is to build something like Tinder. So user can swipe right or left, but I failed at the Gesture, the card won't event move
import SwiftUI
struct ContentView: View {
var users: [User] = [
User(id: "1", imageName: "Card2", position: CGSize.zero),
User(id: "2", imageName: "Card3", position: CGSize.zero),
User(id: "3", imageName: "Card4", position: CGSize.zero)
]
var body: some View {
ZStack {
ForEach(users, id: \.imageName) { user in
CardView(imageName: user.imageName, position: user.position)
.offset(x: user.position.width, y: user.position.height)
.animation(.spring(response: 0.3, dampingFraction: 0.6, blendDuration: 0))
.gesture(
DragGesture()
.onChanged { value in
user.position = value.translation
}
.onEnded { value in
user.position = .zero
}
)
}
}
}
}
This is the user struct
struct User: Identifiable {
var id: String
var imageName: String
#State var position: CGSize
}
The Card, it doesn't move when I try to move it.

How to make each element of List of String clickable?

I have the function below which provides me hashtags from a text.
Now, I am trying to make each of them clickable like this, but it does not work:
for element in msg.findHashtags(){
Button(action: {
print("go to the hashtags view")
}) {
Text(element).background(Color("bg"))
.foregroundColor(.white)
.clipShape(Capsule())
}
}
The answer should be like this:
ForEach( msg.findHashtags(), id: \.self ){element in
Button(action: {
print("go to the hashtags view")
}) {
Text(element).background(Color("bg"))
.foregroundColor(.white)
.clipShape(Capsule())
}
}
Here is an extension template for the autoWordwrapping:
struct PositionKey : PreferenceKey {
static var defaultValue : [[Int]] = []
static func reduce(value: inout [[Int]], nextValue: () -> [[Int]]) {
let next = nextValue()
if next.count == 2 { value += [next.first!]}
else { value.replaceSubrange(((value.count - 1) ..< value.count), with: [value.last! + next[0]]) }
}
typealias Value = [[Int]]
}
struct TextLink: View {
let array = ["tage1111111111ddfff11111","tag2","taffffg3","tafffg4","tag4", "taffffg3","tafffg4","tag4","tag4333ddffdd333333333","ta33333333","tag4333333333333",]
var body: some View {
var tempCurrentPosition : CGFloat = 0
var currentPosition : CGFloat = 0
return ZStack(alignment: .leading){
GeometryReader{ proxy in
HStack{
ForEach( self.array.indices , id: \.self ) { index in
TextTag(text: self.array[index]).fixedSize(horizontal: true, vertical: false).anchorPreference(key: PositionKey.self , value: .leading) { (value: Anchor<CGPoint>) in
if currentPosition == 0 { currentPosition = proxy[value].x}
if proxy.size.width > (proxy[value].x - currentPosition) { tempCurrentPosition = proxy[value].x
return [[index]]}
currentPosition = proxy[value].x
return [[index],[]]
}.transformAnchorPreference(key: PositionKey.self, value: .trailing) { ( currentValue, value: Anchor<CGPoint>) in
if currentValue.count == 1{
if proxy.size.width < (proxy[value].x - currentPosition) {
currentPosition = tempCurrentPosition
currentValue = [currentValue.first!, []]}
}
}
}
}.opacity(0.0).overlayPreferenceValue(PositionKey.self) { v -> AnyView in
return AnyView( VStack(alignment: .leading, spacing: 10){
ForEach(v , id: \.self){ row in
HStack{
ForEach(row , id: \.self){ item in
TextTag(text: self.array[item])
}
}
}
}.opacity(1.0)
)
}
}
}
}
}
struct TextTag: View {
var text: String
var body: some View {
Button(action:{print(self.text)}){
Text(text).padding().background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}}
}

Resources