I have a barcode scanner that is using Vision, and it works great. How do I get the camera to play nicely with my VStack? Currently when I add the cameraView, the cameraView doesn't play nicely with the VStack, meaning it gets added as AVCaptureVideoPreviewLayer frame that I have to use to display the camera.
How can I make this work with the VStack so it's easier modified?
struct BarcodeScannerView: View {
var pitchDegrees: CGFloat
var cameraView = CameraView()
var body: some View {
VStack(spacing: 20) {
Rectangle()
.frame(width: 40, height: 5)
.cornerRadius(3)
Text("Barcode Scanner")
.multilineTextAlignment(.center)
.font(.body)
Spacer()
cameraView
Spacer()
Button(action: {
print("Scan")
}, label: {
Text("Scan")
})
.frame(width: 200, height: 44)
.background(Color.black)
.foregroundColor(Color.white)
.clipShape(Rectangle())
}//VStack
.frame(maxWidth: .infinity)
.frame(height: 300)
.padding()
.background(Color("Warm White"))
.clipShape(Rectangle())
.shadow(radius: 5)
.padding(.all, 5.0)
}//body
}
CameraView
import Foundation
import SwiftUI
struct CameraView: UIViewControllerRepresentable {
typealias UIViewControllerType = CameraViewController
private let cameraViewController: CameraViewController
init() {
cameraViewController = CameraViewController()
}//init
func makeUIViewController(context: Context) -> CameraViewController {
cameraViewController
}//makeUIViewController
func updateUIViewController(_ uiViewController: CameraViewController, context: Context) {
}//updateUIViewController
}//struct
CameraViewController
class CameraViewController: UIViewController {
...
func setupPreview() {
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.videoGravity = .resizeAspectFill
previewLayer.connection?.videoOrientation = .portrait
previewLayer.frame = CGRect(x: 0, y: 0, width: view.frame.width - 30, height: 200)
previewLayer.cornerRadius = 15.0
previewLayer.borderWidth = 1
previewLayer.borderColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
previewLayer.shadowRadius = 5.0
view.layer.addSublayer(previewLayer)
}//setupPreview
...
}
Related
I would make possible to take two photos, with one UIViewControllerRepresentable and Coordinator, but photo will not appear, why?
struct ContentView: View {
#State var camera = false
#State private var holeTop: UIImage?
#State private var holeBottom: UIImage?
#State private var selectedImage: UIImage?
var body: some View {
VStack(spacing: 0) {
Form {
Section {
HStack {
Circle()
.foregroundColor(.red)
.shadow(radius: 4)
.frame(width: 44, height: 44)
.overlay {
holeTop != nil ? Image(uiImage: holeTop!).resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white) :
Image(systemName: "camera")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white)
}.onTapGesture {
selectedImage = holeTop
camera = true
}.padding(24)
Text("Hole top")
.foregroundColor(.gray)
}
HStack {
Circle()
.foregroundColor(.red)
.shadow(radius: 4)
.frame(width: 44, height: 44)
.overlay {
holeTop != nil ? Image(uiImage: holeBottom!).resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white) :
Image(systemName: "camera")
.resizable()
.scaledToFit()
.frame(width: 24, height: 24)
.foregroundColor(.white)
}.onTapGesture {
selectedImage = holeBottom
camera = true
}.padding(24)
Text("Hole bottom")
.foregroundColor(.gray)
}
} header: {
Text("")
}
}
}.sheet(isPresented: $camera) {
CameraView(selectedImage: $selectedImage)
}
}
}
struct CameraView: UIViewControllerRepresentable {
#Binding var selectedImage: UIImage?
#Environment(\.presentationMode) var isPresented
func makeUIViewController(context: Context) -> UIImagePickerController {
let picker = UIImagePickerController()
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {
uiViewController.sourceType = .camera
}
func makeCoordinator() -> Coordinator {
Coordinator(picker: self)
}
}
class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
var picker: CameraView
init(picker: CameraView) {
self.picker = picker
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard let selectedImage = info[.originalImage] as? UIImage else { return }
self.picker.selectedImage = selectedImage
self.picker.isPresented.wrappedValue.dismiss()
}
}
I've created this masked blur effect (code below), it runs in SwiftUI, but uses UIViewRepresentable for the masking, is it possible to re-create the same effect, but just in pure SwiftUI?
Here's the current code, if you run it, use your finger to drag on the screen, this moves the mask to reveal underneath.
import SwiftUI
import UIKit
struct TestView: View {
#State var position: CGPoint = .zero
var simpleDrag: some Gesture {
DragGesture()
.onChanged { value in
self.position = value.location
}
}
var body: some View {
ZStack {
Circle()
.fill(Color.green)
.frame(height: 200)
Circle()
.fill(Color.pink)
.frame(height: 200)
.offset(x: 50, y: 100)
Circle()
.fill(Color.orange)
.frame(height: 100)
.offset(x: -50, y: 00)
BlurView(style: .light, position: $position)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.gesture(
simpleDrag
)
}
}
struct BlurView: UIViewRepresentable {
let style: UIBlurEffect.Style
#Binding var position: CGPoint
func makeUIView(context: UIViewRepresentableContext<BlurView>) -> UIView {
let view = UIView(frame: .zero)
view.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: style)
let blurView = UIVisualEffectView(effect: blurEffect)
blurView.translatesAutoresizingMaskIntoConstraints = false
view.insertSubview(blurView, at: 0)
NSLayoutConstraint.activate([
blurView.heightAnchor.constraint(equalTo: view.heightAnchor),
blurView.widthAnchor.constraint(equalTo: view.widthAnchor),
])
let clipPath = UIBezierPath(rect: UIScreen.main.bounds)
let circlePath = UIBezierPath(ovalIn: CGRect(x: 100, y: 0, width: 200, height: 200))
clipPath.append(circlePath)
let layer = CAShapeLayer()
layer.path = clipPath.cgPath
layer.fillRule = .evenOdd
view.layer.mask = layer
view.layer.masksToBounds = true
return view
}
func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<BlurView>) {
let clipPath = UIBezierPath(rect: UIScreen.main.bounds)
let circlePath = UIBezierPath(ovalIn: CGRect(x: position.x, y: position.y, width: 200, height: 200))
clipPath.append(circlePath)
let layer = CAShapeLayer()
layer.path = clipPath.cgPath
layer.fillRule = .evenOdd
uiView.layer.mask = layer
uiView.layer.masksToBounds = true
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
I think I nearly have a solution, I can use a viewmodifer to render the result twice on top of each other with a ZStack, I can blur one view, and use a mask to knock a hole in it.
import SwiftUI
struct TestView2: View {
#State var position: CGPoint = .zero
var simpleDrag: some Gesture {
DragGesture()
.onChanged { value in
self.position = value.location
}
}
var body: some View {
ZStack {
Circle()
.fill(Color.green)
.frame(height: 200)
Circle()
.fill(Color.pink)
.frame(height: 200)
.offset(x: 50, y: 100)
Circle()
.fill(Color.orange)
.frame(height: 100)
.offset(x: -50, y: 00)
}
.maskedBlur(position: $position)
.gesture(
simpleDrag
)
}
}
struct MaskedBlur: ViewModifier {
#Binding var position: CGPoint
/// Render the content twice
func body(content: Content) -> some View {
ZStack {
content
content
.blur(radius: 10)
.mask(
Hole(position: $position)
.fill(style: FillStyle(eoFill: true))
.frame(maxWidth: .infinity, maxHeight: .infinity)
)
}
}
}
extension View {
func maskedBlur(position: Binding<CGPoint>) -> some View {
self.modifier(MaskedBlur(position: position))
}
}
struct Hole: Shape {
#Binding var position: CGPoint
func path(in rect: CGRect) -> Path {
var path = Path()
path.addRect(UIScreen.main.bounds)
path.addEllipse(in: CGRect(x: position.x, y: position.y, width: 200, height: 200))
return path
}
}
#if DEBUG
struct TestView2_Previews: PreviewProvider {
static var previews: some View {
TestView2()
}
}
#endif
I'm new to SwiftUI and Combine. What I trying to build is a manual camera app, and there's only 4 UI component:
CaptureButton for making a shot from the camera
FocusPicker for controlling manually camera focus exposure
OffsetView for displaying a level of exposure
CameraPreviewRepresentable for integrating UIKit camera into SwiftUI view
Also added Privacy requests into.Info.plist file from a user to allow camera feature and saving to Apple Photo App
For updating data and passing it to the UI, I'm using CameraViewModel, currentCameraSubject and currentCamera Publisher to showing new values from AVCaptureDevice and setting it to CameraViewModel.
And I'm noticing a really interesting behavior/bug of FocusPicker when I start interacting with it and piking a new focus it constantly get back to started position and when OffsetView is getting a new value each time.
But interesting enough for example when OffsetView has the same value then FocusPicker is doing normal. And I do not know why this is happening. Please help, it's really frustrating to fix for me.
By the way, it will only work on a real device only.
Here's all the code:
import SwiftUI
//#main
//struct StackOverflowCamApp: App {
// var cameraViewModel = CameraViewModel(focusLensPosition: 0)
// let cameraController: CustomCameraController = CustomCameraController()
//
// var body: some Scene {
// WindowGroup {
// ContentView(cameraViewModel: cameraViewModel, cameraController: cameraController)
// }
// }
//}
struct ContentView: View {
#State private var didTapCapture = false
#ObservedObject var cameraViewModel: CameraViewModel
let cameraController: CustomCameraController
var body: some View {
VStack {
ZStack {
CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel, cameraController: cameraController)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
VStack {
FocusPicker(selectedFocus: $cameraViewModel.focusChoice)
Text(String(format: "%.2f", cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
Spacer()
OffsetView(levelValue: cameraViewModel.exposureTargetOffset, height: 100)
.frame(maxWidth: .infinity, alignment: .leading)
CaptureButton(didTapCapture: $didTapCapture)
.frame(width: 100, height: 100, alignment: .center)
.padding(.bottom, 20)
}
}
}
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 OffsetView: View {
var levelValue: Float
let height: CGFloat
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.red)
.frame(maxWidth: height / 2, maxHeight: height, alignment: .trailing)
Rectangle()
.foregroundColor(.orange)
.frame(maxWidth: height / 2, maxHeight: height / 20, alignment: .trailing)
.offset(x: 0, y: min(CGFloat(-levelValue) * height / 2, height / 2))
}
}
}
struct FocusPicker: View {
#Binding var selectedFocus: FocusChoice
var body: some View {
Picker(selection: $selectedFocus, label: Text("")) {
ForEach(0..<FocusChoice.allCases.count) {
Text("\(FocusChoice.allCases[$0].caption)")
.foregroundColor(.white)
.font(.subheadline)
.fontWeight(.medium)
.tag(FocusChoice.allCases[$0])
}
.animation(.none)
.background(Color.clear)
.pickerStyle(WheelPickerStyle())
}
.frame(width: 60, height: 200)
.border(Color.gray, width: 5)
.clipped()
}
}
import SwiftUI
import Combine
import AVFoundation
struct CameraPreviewRepresentable: UIViewControllerRepresentable {
#Environment(\.presentationMode) var presentationMode
#Binding var didTapCapture: Bool
#ObservedObject var cameraViewModel: CameraViewModel
let cameraController: CustomCameraController
func makeUIViewController(context: Context) -> CustomCameraController {
cameraController.delegate = context.coordinator
return cameraController
}
func updateUIViewController(_ cameraViewController: CustomCameraController, context: Context) {
if didTapCapture {
cameraViewController.didTapRecord()
}
// checking if new value is differnt from the previous value
if cameraViewModel.focusChoice.rawValue != cameraViewController.manualFocusValue {
cameraViewController.manualFocusValue = cameraViewModel.focusChoice.rawValue
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self, cameraViewModel: cameraViewModel)
}
class Coordinator: NSObject, UINavigationControllerDelegate, AVCapturePhotoCaptureDelegate {
let parent: CameraPreviewRepresentable
var cameraViewModel: CameraViewModel
var tokens = Set<AnyCancellable>()
init(_ parent: CameraPreviewRepresentable, cameraViewModel: CameraViewModel) {
self.parent = parent
self.cameraViewModel = cameraViewModel
super.init()
// for showing focus lens position
self.parent.cameraController.currentCamera
.filter { $0 != nil }
.flatMap { $0!.publisher(for: \.lensPosition) }
.assign(to: \.focusLensPosition, on: cameraViewModel)
.store(in: &tokens)
// for showing exposure offset
self.parent.cameraController.currentCamera
.filter { $0 != nil }
.flatMap { $0!.publisher(for: \.exposureTargetOffset) }
.assign(to: \.exposureTargetOffset, on: cameraViewModel)
.store(in: &tokens)
}
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()
}
}
}
import Combine
import AVFoundation
class CameraViewModel: ObservableObject {
#Published var focusLensPosition: Float = 0
#Published var exposureTargetOffset: Float = 0
#Published var focusChoice: FocusChoice = .infinity
private var tokens = Set<AnyCancellable>()
init(focusLensPosition: Float) {
self.focusLensPosition = focusLensPosition
}
}
enum FocusChoice: Float, CaseIterable {
case infinity = 1
case ft_30 = 0.95
case ft_15 = 0.9
case ft_10 = 0.85
case ft_7 = 0.8
case ft_5 = 0.5
case ft_4 = 0.7
case ft_3_5 = 0.65
case ft_3 = 0.6
case auto = 0
}
extension FocusChoice {
var caption: String {
switch self {
case .infinity: return "∞ft"
case .ft_30: return "30"
case .ft_15: return "15"
case .ft_10: return "10"
case .ft_7: return "7"
case .ft_5: return "5"
case .ft_4: return "4"
case .ft_3_5: return "3.5"
case .ft_3: return "3"
case .auto: return "Auto"
}
}
}
import UIKit
import Combine
import AVFoundation
class CustomCameraController: UIViewController {
var image: UIImage?
var captureSession = AVCaptureSession()
var backCamera: AVCaptureDevice?
var frontCamera: AVCaptureDevice?
lazy var currentCamera: AnyPublisher<AVCaptureDevice?, Never> = currentCameraSubject.eraseToAnyPublisher()
var photoOutput: AVCapturePhotoOutput?
var cameraPreviewLayer: AVCaptureVideoPreviewLayer?
private var currentCameraSubject = CurrentValueSubject<AVCaptureDevice?, Never>(nil)
var manualFocusValue: Float = 1 {
didSet {
guard manualFocusValue != 0 else {
setAutoLensPosition()
return
}
setFocusLensPosition(manualValue: manualFocusValue)
}
}
//DELEGATE
var delegate: AVCapturePhotoCaptureDelegate?
func setFocusLensPosition(manualValue: Float) {
do {
try currentCameraSubject.value!.lockForConfiguration()
currentCameraSubject.value!.focusMode = .locked
currentCameraSubject.value!.setFocusModeLocked(lensPosition: manualValue, completionHandler: nil)
currentCameraSubject.value!.unlockForConfiguration()
} catch let error {
print(error.localizedDescription)
}
}
func setAutoLensPosition() {
do {
try currentCameraSubject.value!.lockForConfiguration()
currentCameraSubject.value!.focusMode = .continuousAutoFocus
currentCameraSubject.value!.unlockForConfiguration()
} catch let error {
print(error.localizedDescription)
}
}
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.currentCameraSubject.send(self.backCamera)
}
func setupInputOutput() {
do {
let captureDeviceInput = try AVCaptureDeviceInput(device: currentCameraSubject.value!)
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
self.view.layer.insertSublayer(cameraPreviewLayer!, at: 0)
}
func startRunningCaptureSession() {
captureSession.startRunning()
}
}
Your ContentView gets updated all the time from the Published values. To Fix that we first remove the declaration as ObservedObject from the ViewModel inside the ContentView and declare it like that:
let cameraViewModel: CameraViewModel
Now we will get some errors. For FocusView just use ProxyBinding.
FocusPicker(selectedFocus: Binding<FocusChoice>(
get: {
cameraViewModel.focusChoice
},
set: {
cameraViewModel.focusChoice = $0
}
))
For the updated text, just create another View. Here! we use ObservedObject.
struct TextView: View {
#ObservedObject var cameraViewModel: CameraViewModel
var body: some View {
Text(String(format: "%.2f", cameraViewModel.focusLensPosition))
.foregroundColor(.red)
.font(.largeTitle)
}
}
Same for the OffsetView. Add ObservedObject there
struct OffsetView: View {
#ObservedObject var viewModel : CameraViewModel
let height: CGFloat
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.red)
.frame(maxWidth: height / 2, maxHeight: height, alignment: .trailing)
Rectangle()
.foregroundColor(.orange)
.frame(maxWidth: height / 2, maxHeight: height / 20, alignment: .trailing)
.offset(x: 0, y: min(CGFloat(-viewModel.exposureTargetOffset) * height / 2, height / 2))
}
}
}
The ContentView will then look like that:
struct ContentView: View {
#State private var didTapCapture = false
let cameraViewModel: CameraViewModel
let cameraController: CustomCameraController
var body: some View {
VStack {
ZStack {
CameraPreviewRepresentable(didTapCapture: $didTapCapture, cameraViewModel: cameraViewModel, cameraController: cameraController)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
VStack {
FocusPicker(selectedFocus: Binding<FocusChoice>(
get: {
cameraViewModel.focusChoice
},
set: {
cameraViewModel.focusChoice = $0
}
))
TextView(cameraViewModel: cameraViewModel)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.edgesIgnoringSafeArea(.all)
Spacer()
OffsetView(viewModel: cameraViewModel, height: 100)
.frame(maxWidth: .infinity, alignment: .leading)
CaptureButton(didTapCapture: $didTapCapture)
.frame(width: 100, height: 100, alignment: .center)
.padding(.bottom, 20)
}
}
}
Hence, we do not have any ObservedObject anymore in the ContentView and our Picker just works fine.
Forgive me as I am extremely novice with SwiftUI... I am attempting to pull data from the CMS and put it in my app however it is throwing three errors on each attempt for the data to be retrieved and placed...
The errors are highlighting in the sections that read "api.beers.title", "api.beers.type" and "api.beers.description".
Errors
Value of type 'API' has no dynamic member 'beers' using key path from root type 'API'
Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'ObservedObject.Wrapper'
Initializer 'init(_:)' requires that 'Binding' conform to 'StringProtocol'
API Call Code
func getArray(id: String, completion: #escaping([Entry]) -> ()) {
let query = Query.where(contentTypeId: id)
client.fetchArray(of: Entry.self, matching: query) { result in
switch result {
case .success(let array):
DispatchQueue.main.async {
completion(array.items)
}
case .failure(let error):
print(error)
}
}
}
class API: ObservableObject {
#Published var draft: [Draft] = draftData
init() {
getArray(id: "beers") { (items) in
items.forEach { (item) in
self.draft.append(Draft(
title: item.fields["title"] as! String,
type: item.fields["type"] as! String,
description: item.fields["type"] as! String
))
}
}
}
}
struct LandingPageView: View {
var body: some View {
VStack {
VStack {
Text("Problem Solved")
Text("Brewing Company")
}
.font(.system(size: 24, weight: .bold))
.multilineTextAlignment(.center)
.foregroundColor(Color("TextColor"))
VStack {
Text("NEWS & EVENTS")
.font(.title)
.fontWeight(.bold)
.padding(.top, 40)
.foregroundColor(Color("TextColor"))
NewsTile()
Text("On Draft" .uppercased())
.font(.title)
.fontWeight(.bold)
.padding(.top)
.foregroundColor(Color("TextColor"))
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 20) {
ForEach(draftData) { item in
GeometryReader { geometry in
DraftList(beer: item)
.rotation3DEffect(Angle(degrees: Double(geometry.frame(in: .global).minX - 30) / -20), axis: (x: 0, y: 10.0, z: 0))
}
.frame(width: 275, height: 200)
}
}
.padding(.leading, 30)
.padding(.trailing, 30)
}
}
.frame(width: 400, height: 850)
.background(Color("PageBackground"))
.edgesIgnoringSafeArea(.all)
}
}
}
struct DraftList: View {
var width: CGFloat = 275
var height: CGFloat = 200
#ObservedObject var api = API()
var beer: Draft
var body: some View {
VStack {
Spacer()
Text(api.beers.title)
.font(.system(size: 24, weight: .bold))
.padding(.horizontal, 20)
.frame(width: 275, alignment: .leading)
.foregroundColor(Color("TextColor"))
Text(api.beers.type .uppercased())
.font(.system(size: 14, weight: .bold))
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.horizontal, 20)
Text(api.beers.description)
.font(.system(size: 12))
.padding(.horizontal, 20)
.padding(.top, 10)
Spacer()
HStack {
// Add OnTapGesture to bring to full view + cart options.
Text("Click To Add To Cart")
.font(.footnote)
Image(systemName: "cart")
}
.padding(.bottom)
}
.padding(.horizontal, 20)
.frame(width: width, height: height)
.background(Color("TileOrangeColor"))
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
}
}
Your draft is array, you can access by index like Text(api.draft[0].title)
#Published var draft: [Draft] = draftData instead #Published var draft: [Draft] = []
Updated:
class API: ObservableObject {
#Published var draft: [Draft] = []
init() {
getArray(id: "beers") { (items) in
items.forEach { (item) in
self.draft.append(Draft(
title: item.fields["title"] as! String,
type: item.fields["type"] as! String,
description: item.fields["type"] as! String
))
}
}
}
}
struct DraftList: View {
var width: CGFloat = 275
var height: CGFloat = 200
#ObservedObject var api = API()
var body: some View {
VStack {
ForEach(api.draft) {item in
Spacer()
Text(item.title)
.font(.system(size: 24, weight: .bold))
.padding(.horizontal, 20)
.frame(width: 275, alignment: .leading)
.foregroundColor(Color("TextColor"))
...
}
}
.padding(.horizontal, 20)
.frame(width: width, height: height)
.background(Color("TileOrangeColor"))
.cornerRadius(15)
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
}
}
Attributed View is not updating layout correctly for attributed string. Below is my code.
I have created Attributed Label for SwiftUI.
import SwiftUI
class ViewLabel : UIView {
private var label = UILabel()
override init(frame: CGRect) {
super.init(frame:frame)
self.addSubview(label)
label.numberOfLines = 0
label.autoresizingMask = [.flexibleWidth, .flexibleHeight]
label.lineBreakMode = .byWordWrapping
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
func setString(_ attributedString:NSAttributedString) {
self.label.attributedText = attributedString
}
}
struct AHAttributedTextLabel: UIViewRepresentable {
var attributedString:NSAttributedString
func makeUIView(context: Context) -> ViewLabel {
let view = ViewLabel(frame:CGRect.zero)
return view
}
func updateUIView(_ uiView: ViewLabel, context: UIViewRepresentableContext<AHAttributedTextLabel>) {
uiView.setString(attributedString)
}
}
struct AHAttributedLabel_Previews: PreviewProvider {
static var previews: some View {
AHAttributedTextLabel(attributedString: NSAttributedString(string: "Test"))
}
}
CellView for List which takes 2 parameters title and subtitle
struct AHAttributtedTitleSubtitleView: View {
var title:NSAttributedString
var subTitle:String
var body: some View {
HStack{
VStack(alignment:.leading){
AHAttributedTextLabel(attributedString: self.title)
.font(Font.system(size: 14, weight: .medium, design: .default))
.padding(.top, 10.0)
.padding(.leading, 10.0)
.padding(.trailing, 10.0)
.padding(.bottom, 5.0)
Text(self.subTitle)
.font(Font.system(size: 14, weight: .light, design: .default))
.padding(.leading, 10.0)
.padding(.trailing, 10.0)
.padding(.bottom, 10.0)
}
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.overlay(AHDashBorderView())
}
}
Content View which displays List
import SwiftUI
struct AHVitalGraphContentView: View {
#ObservedObject var viewModel: AHVitalsGraphViewModel
var body: some View {
VStack{
List(self.viewModel.vitalGroup.vitals,id:\.vitalId) { vital in
AHAttributtedTitleSubtitleView(title: vital.attributedString, subTitle: vital.recorderDetails)
}
}.navigationBarTitle(Text(self.viewModel.vitalGroup.latestReadings.vitalName ?? ""), displayMode: .inline)
}
}
below is attached screenshot of issues. u can see .... at top of list cell.