I have this situation:
First View, in this view i got the current position and i save the latitude and longitude in two double type var: doublelat and doublelng
import SwiftUI
struct CoordinateView: View {
#ObservedObject var locationManager = LocationManager()
#State private var gotopage = false
var userLatitude: String {
let lat = String(format: "%.6f", locationManager.lastLocation?.coordinate.latitude ?? 0)
return "\(lat)"
}
var userLongitude: String {
let lng = String(format: "%.6f", locationManager.lastLocation?.coordinate.longitude ?? 0)
return "\(lng)"
}
var doubleLat : Double {
return locationManager.lastLocation?.coordinate.latitude ?? 0
}
var doubleLng : Double {
return locationManager.lastLocation?.coordinate.longitude ?? 0
}
private var getcoordinates: Bool {
if locationManager.statusCode == 0 {
return false
} else {
return true
}
}
var body: some View {
VStack {
HStack() {
Text("Your position")
.font(.headline)
Spacer()
}
VStack(alignment: .leading) {
HStack(){
Text("Lat")
Spacer()
Text("\(userLatitude)")
}.padding(5)
HStack(){
Text("Long")
Spacer()
Text("\(userLongitude)")
}.padding(5)
}
if getcoordinates {
HStack(alignment: .center){
Button("Go to next page") {
self.gotopage = true
}
}.padding()
VStack {
if self.gotopage {
AddressView(latitude: self.doubleLat, longitude: self.doubleLng)
}
}
}
}
.padding()
}
};
struct CoordinateView_Previews: PreviewProvider {
static var previews: some View {
CoordinateView()
}
}
Now just press on button Button("Go to next page"), go to view AddressView(latitude: self.doubleLat, longitude: self.doubleLng), with two param latitude and longitude.
This is AddressView:
import SwiftUI
struct AddressView: View {
var latitude: Double
var longitude: Double
#ObservedObject var apiManager = APIManager(lat: <latitude>, lng: <longitude>)
var body: some View {
.....
.....
}
struct AddressView_Previews: PreviewProvider {
static var previews: some View {
AddressView(latitude: 14.564378, longitude: 42.674532)
} }
In this second view i have to pass the two param (latitude and longitude) from previous view in this declaration:
#ObservedObject var apiManager = APIManager(lat: <latitude>, lng: <longitude>)
How can I do?
Try the following declaration for AddressView
struct AddressView: View {
var latitude: Double
var longitude: Double
#ObservedObject var apiManager: APIManager
init(latitude: Double, longitude: Double) {
self.latitude = latitude
self.longitude = longitude
self.apiManager = APIManager(lat: latitude, lng: longitude)
}
...
Related
I have an app that records costs for a car. I can't work out how to create a field that keeps a running total for the ongoing costs. In the ContentView file I have a struct that defines what an expense is, which includes the 'amount'.
Any help is appreciated. Thanks.
There are 2 files, ContentView, and Addview;
struct ContentView: View {
#StateObject var expenseList = ExpenseList()
#State private var isShowingAddView = false
#State private var totalCost = 0.0
var body: some View {
NavigationView {
VStack {
VStack(alignment: .trailing) {
Text("Total Cost").font(.headline) //just holding a place for future code
}
Form {
List {
ForEach(expenseList.itemList) { trans in
HStack{
Text(trans.item)
.font(.headline)
Spacer()
VStack(alignment: .trailing) {
HStack {
Text("Amount: ")
.font(.caption).bold()
Text(trans.amount, format: .currency(code: "USD"))
.font(.caption)
}
}
}
}
.onDelete(perform: removeItems)
}
}
.navigationTitle("Expenditure")
.toolbar {
Button {
isShowingAddView = true
} label: {
Image(systemName: "plus")
}
}
.sheet(isPresented: $isShowingAddView) {
AddView(expenseList: expenseList)
}
}
}
}
func removeItems(at offsets: IndexSet) {
expenseList.itemList.remove(atOffsets: offsets)
}
}
class ExpenseList: ObservableObject {
#Published var itemList = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(itemList) {
UserDefaults.standard.set(encoded, forKey: "Things")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Things") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
itemList = decodedItems
return
}
}
itemList = []
}
}
struct ExpenseItem: Identifiable, Codable {
var id = UUID()
let item: String
let amount: Double
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct AddView: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var expenseList: ExpenseList
#State private var item = "Fuel"
#State private var amount = 0.0
let itemType = ["Fuel", "Tyres"]
var body: some View {
NavigationView {
Form {
Picker("Type", selection: $item) {
ForEach(itemType, id: \.self) {
Text($0)
}
}
TextField("Enter amount...", value: $amount, format: .currency(code: "USD"))
}
.navigationTitle("Add an item...")
.toolbar {
Button("Save") {
let trans = ExpenseItem(item: item, amount: amount)
expenseList.itemList.append(trans)
dismiss()
}
}
}
}
}
struct AddView_Previews: PreviewProvider {
static var previews: some View {
AddView(expenseList: ExpenseList())
}
}
There are many ways to do ... create a field that keeps a running total for the ongoing costs. This is just one way.
Try this approach, using an extra var totalCost in your ExpenseList and a summation.
class ExpenseList: ObservableObject {
#Published private (set) var totalCost = 0.0 // <-- here
#Published var itemList = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(itemList) {
UserDefaults.standard.set(encoded, forKey: "Things")
}
totalCost = itemList.map{ $0.amount }.reduce(0.0, { $0 + $1 }) // <-- here
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Things") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
itemList = decodedItems
return
}
}
itemList = []
}
}
And use it like this:
Text("Total Cost: \(expenseList.totalCost)").font(.headline)
You can of course do this, without adding any extra var:
Text("Total Cost: \(expenseList.itemList.map{ $0.amount }.reduce(0.0, { $0 + $1 }))")
I have an app that records costs for a car. I can't work out how to create a field that keeps a running total for the ongoing costs. In the ContentView file I have a struct that defines what an expense is, which includes the 'amount'.
Any help is appreciated. Thanks.
There are 2 files, ContentView, and Addview;
struct ContentView: View {
#StateObject var expenseList = ExpenseList()
#State private var isShowingAddView = false
#State private var totalCost = 0.0
var body: some View {
NavigationView {
VStack {
VStack(alignment: .trailing) {
Text("Total Cost").font(.headline) //just holding a place for future code
}
Form {
List {
ForEach(expenseList.itemList) { trans in
HStack{
Text(trans.item)
.font(.headline)
Spacer()
VStack(alignment: .trailing) {
HStack {
Text("Amount: ")
.font(.caption).bold()
Text(trans.amount, format: .currency(code: "USD"))
.font(.caption)
}
}
}
}
.onDelete(perform: removeItems)
}
}
.navigationTitle("Expenditure")
.toolbar {
Button {
isShowingAddView = true
} label: {
Image(systemName: "plus")
}
}
.sheet(isPresented: $isShowingAddView) {
AddView(expenseList: expenseList)
}
}
}
}
func removeItems(at offsets: IndexSet) {
expenseList.itemList.remove(atOffsets: offsets)
}
}
class ExpenseList: ObservableObject {
#Published var itemList = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(itemList) {
UserDefaults.standard.set(encoded, forKey: "Things")
}
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Things") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
itemList = decodedItems
return
}
}
itemList = []
}
}
struct ExpenseItem: Identifiable, Codable {
var id = UUID()
let item: String
let amount: Double
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct AddView: View {
#Environment(\.dismiss) var dismiss
#ObservedObject var expenseList: ExpenseList
#State private var item = "Fuel"
#State private var amount = 0.0
let itemType = ["Fuel", "Tyres"]
var body: some View {
NavigationView {
Form {
Picker("Type", selection: $item) {
ForEach(itemType, id: \.self) {
Text($0)
}
}
TextField("Enter amount...", value: $amount, format: .currency(code: "USD"))
}
.navigationTitle("Add an item...")
.toolbar {
Button("Save") {
let trans = ExpenseItem(item: item, amount: amount)
expenseList.itemList.append(trans)
dismiss()
}
}
}
}
}
struct AddView_Previews: PreviewProvider {
static var previews: some View {
AddView(expenseList: ExpenseList())
}
}
There are many ways to do ... create a field that keeps a running total for the ongoing costs. This is just one way.
Try this approach, using an extra var totalCost in your ExpenseList and a summation.
class ExpenseList: ObservableObject {
#Published private (set) var totalCost = 0.0 // <-- here
#Published var itemList = [ExpenseItem]() {
didSet {
if let encoded = try? JSONEncoder().encode(itemList) {
UserDefaults.standard.set(encoded, forKey: "Things")
}
totalCost = itemList.map{ $0.amount }.reduce(0.0, { $0 + $1 }) // <-- here
}
}
init() {
if let savedItems = UserDefaults.standard.data(forKey: "Things") {
if let decodedItems = try? JSONDecoder().decode([ExpenseItem].self, from: savedItems) {
itemList = decodedItems
return
}
}
itemList = []
}
}
And use it like this:
Text("Total Cost: \(expenseList.totalCost)").font(.headline)
You can of course do this, without adding any extra var:
Text("Total Cost: \(expenseList.itemList.map{ $0.amount }.reduce(0.0, { $0 + $1 }))")
Why has only the orange Color a right animation? Green and Red is laying under the list while the animation, but why?
With VStavk there is no problem but with list. Want an animation when switching from list View to Grid View.
struct Colors: Identifiable{
var id = UUID()
var col: Color
}
struct ContentView: View {
#State var on = true
#Namespace var ani
var colors = [Colors(col: .green),Colors(col: .orange),Colors(col: .red)]
var body: some View {
VStack {
if on {
List{
ForEach(colors){col in
col.col
.matchedGeometryEffect(id: "\(col.id)", in: ani)
.animation(.easeIn)
}
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
}
.listStyle(InsetGroupedListStyle())
.frame(height: 400)
} else {
LazyVGrid(columns: [GridItem(.fixed(200)),GridItem(.fixed(200))], content: {
ForEach(colors){col in
col.col
.matchedGeometryEffect(id: "\(col.id)", in: ani)
.animation(.easeIn)
}
})
.frame(height: 400)
}
Button("toggle"){
withAnimation(.easeIn){
on.toggle()
}
}
}
}
Thanks to the comment of #Asperi and this post: Individually modifying child views passed to a container using #ViewBuilder in SwiftUI with the answer of #Tushar Sharma I tried something like this:
import SwiftUI
struct SomeContainerView<Content: View>:View {
var ani: Namespace.ID
var model:[Model] = []
init(namespace: Namespace.ID,model:[Model],#ViewBuilder content: #escaping (Model) -> Content) {
self.content = content
self.model = model
ani = namespace
}
let content: (Model) -> Content
var body: some View {
VStack{
ForEach(model,id:\.id){model in
content(model)
.background(Color.gray.matchedGeometryEffect(id: model.id, in: ani))
}
}
}
}
struct ContentView:View {
#ObservedObject var modelData = Objects()
#Namespace var ani
#State var show = true
var body: some View{
VStack{
Toggle("toggle", isOn: $show.animation())
if show{
SomeContainerView(namespace: ani,model: modelData.myObj){ data in
HStack{
Text("\(data.name)")
data.color.frame(width: 100,height : 100)
}
}
}else{
LazyVGrid(columns: [GridItem(.fixed(110)),GridItem(.fixed(110))],spacing: 10){
ForEach(modelData.myObj){model in
Text("\(model.name)")
.frame(width: 100,height: 100)
.background(Color.gray.matchedGeometryEffect(id: model.id, in: ani))
}
}
}
}
}
}
struct Model: Identifiable{
var id = UUID().uuidString
var name:String
var color:Color
init(name:String,color:Color) {
self.name = name
self.color = color
}
}
class Objects:ObservableObject{
#Published var myObj:[Model] = []
init() {
initModel()
}
func initModel(){
let model = Model(name: "Jack", color: .green)
let model1 = Model(name: "hey Jack", color: .red)
let model2 = Model(name: "hey billy", color: .red)
myObj.append(model)
myObj.append(model1)
myObj.append(model2)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm new to SwiftUI and manual camera functionality, and I really need help.
So I trying to build a SwiftUI camera view that has a UIKit camera as a wrapper to control the focus lens position via SwiftUI picker view, display below a fucus value, and want to try have a correlation between AVcaptureDevice.lensPosition from 0 to 1.0 and feats that are displayed in the focus picker view. But for now, I only want to display that fucus number on screen.
And the problem is when I try to update focus via coordinator focus observation and set it to the camera view model then nothing happened. Please help 🙌
Here's the code:
import SwiftUI
import AVFoundation
import Combine
struct ContentView: View {
#State private var didTapCapture = false
#State private var focusLensPosition: Float = 0
#ObservedObject var cameraViewModel = CameraViewModel(focusLensPosition: 0)
var body: some View {
VStack {
ZStack {
CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
VStack {
FocusPicker(selectedFocus: $focusLensPosition)
Text(String(cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
Spacer()
CaptureButton(didTapCapture: $didTapCapture)
.frame(width: 100, height: 100, alignment: .center)
.padding(.bottom, 20)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct CaptureButton: View {
#Binding var didTapCapture : Bool
var body: some View {
Button {
didTapCapture.toggle()
} label: {
Image(systemName: "photo")
.font(.largeTitle)
.padding(30)
.background(Color.red)
.foregroundColor(.white)
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.red)
)
}
}
}
struct CameraPreviewRepresentable: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var didTapCapture: Bool
#ObservedObject var cameraViewModel: CameraViewModel
let cameraController: CustomCameraController = CustomCameraController()
func makeUIViewController(context: Context) -> CustomCameraController {
cameraController.delegate = context.coordinator
return cameraController
}
func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
if (self.didTapCapture) {
cameraViewController.didTapRecord()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self, cameraViewModel: cameraViewModel)
}
class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
let parent: CameraPreviewRepresentable
var cameraViewModel: CameraViewModel
var focusLensPositionObserver: NSKeyValueObservation?
init(_ parent: CameraPreviewRepresentable, cameraViewModel: CameraViewModel) {
self.parent = parent
self.cameraViewModel = cameraViewModel
super.init()
focusLensPositionObserver = self.parent.cameraController.currentCamera?.observe(\.lensPosition, options: [.new]) { [weak self] camera, _ in
print(Float(camera.lensPosition))
//announcing changes via Publisher
self?.cameraViewModel.focusLensPosition = camera.lensPosition
}
}
deinit {
focusLensPositionObserver = nil
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
parent.didTapCapture = false
if let imageData = photo.fileDataRepresentation(), let image = UIImage(data: imageData) {
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
parent.presentationMode.wrappedValue.dismiss()
}
}
}
class CameraViewModel: ObservableObject {
#Published var focusLensPosition: Float = 0
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
class CustomCameraController: UIViewController {
var image: UIImage?
var captureSession = AVCaptureSession()
var backCamera: AVCaptureDevice?
var frontCamera: AVCaptureDevice?
var currentCamera: AVCaptureDevice?
var photoOutput: AVCapturePhotoOutput?
var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
//DELEGATE
var delegate: AVCapturePhotoCaptureDelegate?
func showFocusLensPosition() -> Float {
// guard let camera = currentCamera else { return 0 }
// try! currentCamera!.lockForConfiguration()
// currentCamera!.focusMode = .autoFocus
//// currentCamera!.setFocusModeLocked(lensPosition: currentCamera!.lensPosition, completionHandler: nil)
// currentCamera!.unlockForConfiguration()
return currentCamera!.lensPosition
}
func didTapRecord() {
let settings = AVCapturePhotoSettings()
photoOutput?.capturePhoto(with: settings, delegate: delegate!)
}
override func viewDidLoad() {
super.viewDidLoad()
setup()
}
func setup() {
setupCaptureSession()
setupDevice()
setupInputOutput()
setupPreviewLayer()
startRunningCaptureSession()
}
func setupCaptureSession() {
captureSession.sessionPreset = .photo
}
func setupDevice() {
let deviceDiscoverySession =
AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera],
mediaType: .video,
position: .unspecified)
for device in deviceDiscoverySession.devices {
switch device.position {
case .front:
self.frontCamera = device
case .back:
self.backCamera = device
default:
break
}
}
self.currentCamera = self.backCamera
}
func setupInputOutput() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: currentCamera!)
captureSession.addInput(captureDeviceInput)
photoOutput = AVCapturePhotoOutput()
captureSession.addOutput(photoOutput!)
} catch {
print(error)
}
}
func setupPreviewLayer() {
self.cameraPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
self.cameraPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
let deviceOrientation = UIDevice.current.orientation
cameraPreviewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation(rawValue: deviceOrientation.rawValue)!
self.cameraPreviewLayer?.frame = self.view.frame
// view.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
}
func startRunningCaptureSession() {
captureSession.startRunning()
}
}
struct FocusPicker: View {
var feets = ["∞ ft", "30", "15", "10", "7", "5", "4", "3.5", "3", "2.5", "2", "1.5", "1", "0.5", "Auto"]
#Binding var selectedFocus: Float
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(0 ..< feets.count) {
Text(feets[$0])
.foregroundColor(.white)
.font(.subheadline)
.fontWeight(.medium)
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}
The problem with your provided code is that the type of selectedFocus within the FocusPicker view should be Integer rather than Float. So one option is to change this type to Integer and find a way to express the AVCaptureDevice.lensPosition as an Integer with the given range.
The second option is to replace the feets array with an enumeration. By making the enumeration conform to the CustomStringConvertible protocol, you can even provide a proper description. Please see my example below.
I've stripped your code a bit as you just wanted to display the number in the first step and thus the code is more comprehensible.
My working example:
import SwiftUI
import Combine
struct ContentView: View {
#ObservedObject var cameraViewModel = CameraViewModel(focusLensPosition: 0.5)
var body: some View {
VStack {
ZStack {
VStack {
FocusPicker(selectedFocus: $cameraViewModel.focusLensPosition)
Text(String(self.cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class CameraViewModel: ObservableObject {
#Published var focusLensPosition: Float
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
enum Feets: Float, CustomStringConvertible, CaseIterable, Identifiable {
case case1 = 0.0
case case2 = 0.5
case case3 = 1.0
var id: Float { self.rawValue }
var description: String {
get {
switch self {
case .case1:
return "∞ ft"
case .case2:
return "4"
case .case3:
return "Auto"
}
}
}
}
struct FocusPicker: View {
#Binding var selectedFocus: Float
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(Feets.allCases) { feet in
Text(feet.description)
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}
I am experiencing the following animation of the text in the selected segment of Segmented Controls when the View is refreshed after changing some other data in the View:
Is this a bug/feature or is there a way to eliminate this behaviour?
This is the code to reproduce the effect:
import SwiftUI
struct ContentView: View {
let colorNames1 = ["Red", "Green", "Blue"]
#State private var color1 = 0
let colorNames2 = ["Yellow", "Purple", "Orange"]
#State private var color2 = 0
var body: some View {
VStack {
VStack {
Picker(selection: $color1, label: Text("Color")) {
ForEach(0..<3, id: \.self) { index in
Text(self.colorNames1[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Color 1: \(color1)")
}
.padding()
VStack {
Picker(selection: $color2, label: Text("Color")) {
ForEach(0..<3, id: \.self) { index in
Text(self.colorNames2[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Color 2: \(color2)")
}
.padding()
}
}
}
This was run under iOS 13.4 / Xcode 11.4
rearrange you code base ... (this helps SwiftUI to "refresh" only necessary Views)
import SwiftUI
struct ContentView: View {
let colorNames1 = ["Red", "Green", "Blue"]
#State private var color1 = 0
let colorNames2 = ["Yellow", "Purple", "Orange"]
#State private var color2 = 0
var body: some View {
VStack {
MyPicker(colorNames: colorNames1, color: $color1)
.padding()
MyPicker(colorNames: colorNames2, color: $color2)
.padding()
}
}
}
struct MyPicker: View {
let colorNames: [String]
#Binding var color: Int
var body: some View {
VStack {
Picker(selection: $color, label: Text("Color")) {
ForEach(0..<colorNames.count) { index in
Text(self.colorNames[index]).tag(index)
}
}.pickerStyle(SegmentedPickerStyle())
Text("Color 1: \(color)")
}
}
}
struct ContetView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
result
I created a custom SegmentControl to solve this problem:
import SwiftUI
struct MyTextPreferenceKey: PreferenceKey {
typealias Value = [MyTextPreferenceData]
static var defaultValue: [MyTextPreferenceData] = []
static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) {
value.append(contentsOf: nextValue())
}
}
struct MyTextPreferenceData: Equatable {
let viewIndex: Int
let rect: CGRect
}
struct SegmentedControl : View {
#Binding var selectedIndex: Int
#Binding var rects: [CGRect]
#Binding var titles: [String]
var body: some View {
ZStack(alignment: .topLeading) {
SelectedView()
.frame(width: rects[selectedIndex].size.width - 4, height: rects[selectedIndex].size.height - 4)
.offset(x: rects[selectedIndex].minX + 2, y: rects[selectedIndex].minY + 2)
.animation(.easeInOut(duration: 0.5))
VStack {
self.addTitles()
}.onPreferenceChange(MyTextPreferenceKey.self) { preferences in
for p in preferences {
self.rects[p.viewIndex] = p.rect
}
}
}.background(Color(.red)).clipShape(Capsule()).coordinateSpace(name: "CustomSegmentedControl")
}
func totalSize() -> CGSize {
var totalSize: CGSize = .zero
for rect in rects {
totalSize.width += rect.width
totalSize.height = rect.height
}
return totalSize
}
func addTitles() -> some View {
HStack(alignment: .center, spacing: 8, content: {
ForEach(0..<titles.count) { index in
return SegmentView(selectedIndex: self.$selectedIndex, label: self.titles[index], index: index, isSelected: self.segmentIsSelected(selectedIndex: self.selectedIndex, segmentIndex: index))
}
})
}
func segmentIsSelected(selectedIndex: Int, segmentIndex: Int) -> Binding<Bool> {
return Binding(get: {
return selectedIndex == segmentIndex
}) { (value) in }
}
}
struct SegmentView: View {
#Binding var selectedIndex: Int
let label: String
let index: Int
#Binding var isSelected: Bool
var body: some View {
Text(label)
.padding(.vertical, 6)
.padding(.horizontal, 10)
.foregroundColor(Color(.label))
.background(MyPreferenceViewSetter(index: index)).onTapGesture {
self.selectedIndex = self.index
}
}
}
struct MyPreferenceViewSetter: View {
let index: Int
var body: some View {
GeometryReader { geometry in
Rectangle()
.fill(Color.clear)
.preference(key: MyTextPreferenceKey.self,
value: [MyTextPreferenceData(viewIndex: self.index, rect: geometry.frame(in: .named("CustomSegmentedControl")))])
}
}
}
struct SelectedView: View {
var body: some View {
Capsule()
.fill(Color(.systemBackground))
.edgesIgnoringSafeArea(.horizontal)
}
}
result