#ObservedObject not publishing changes to View - ios

I have a View with a Text that is meant to be updated based on a CountDown() value secondsLeft:
struct TimerCapsule: View {
#ObservedObject var countdown = CountDown()
var body: some View {
Text("\(self.countdown.secondsLeft)")
.font(.system(size: 30))
.foregroundColor(.white)
.frame(width: 100, height: 40)
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.background(
Capsule()
.fill(Color("Exit"))
.opacity(0.7)
)
.onAppear{
print("Start countdown")
self.countdown.start(from: 30)
}
.onDisappear {
self.countdown.stop()
}
}
}
Here is the CountDown class below. print("Seconds left: \(self.secondsLeft)") successfully prints 30, 29, 28 etc - however, this is not updated in the View above. There is only 1 instance been used in the view above so I'm not sure why it is not being reflected in the Text("\(self.countdown.secondsLeft)") above.
Timer:
class CountDown: ObservableObject {
#Published var secondsLeft = 30
#ObservedObject var block = Block()
var timer: Timer!
func start(from seconds: Int){
self.secondsLeft = seconds
print("start timer")
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
if (self.secondsLeft == 1){
switch self.block.type {
case .match:
self.block.pass()
case .pass:
self.block.pass()
default:
self.block.delete()
}
}
self.secondsLeft -= 1
print("Seconds left: \(self.secondsLeft)")
}
}
func stop(){
self.timer?.invalidate()
}
}
Any idea?
EDIT:
ContentView.swift
struct ContentView: View {
#ObservedObject var auth = UserAuth()
var body: some View {
Group {
if auth.uid != nil { HomeView(auth: auth) } else { LoginView(auth: auth) }
}
}
}
HomeView.swift
struct HomeView: View {
let auth: UserAuth
#ObservedObject var block = Block()
init(auth: UserAuth) {
self.auth = auth
UITabBar.appearance().barTintColor = UIColor.orange
UITabBar.appearance().unselectedItemTintColor = UIColor.white.withAlphaComponent(0.5)
UITabBar.appearance().autoresizesSubviews = true
}
#State var selectedTab = 1
#ObservedObject var locationManager = LocationManager()
var body: some View {
ZStack {
TabView(selection: $selectedTab) {
Settings(auth: auth)
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(0)
ZStack {
MapView(locationManager: locationManager)
Color.black.opacity(self.block.status.opacity).animation(nil)
VStack {
if self.block.type == Type.match || self.block.type == Type.pass {
VStack{
TimerCapsule()
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity, alignment: .topLeading)
.padding(.top, 20)
}
Spacer()
if self.block.type == Type.create || self.block.type == Type.delete || self.block.type == Type.pass{
StatusButton()
}
}.padding(40)
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}.edgesIgnoringSafeArea(.top)
.tabItem {
Image(systemName: "location.circle.fill")
Text("Home")
}.tag(1)
ProfileView()
.tabItem {
Image(systemName: "person")
Text("Profile")
}.tag(2)
}.accentColor(Color.white)
.font(.headline)
}
}
}

Can't reproduce any issue. You didn't give enough information to allow the entire code to compile, but if we remove references to all the stuff you left out, we get this:
class CountDown: ObservableObject {
#Published var secondsLeft = 30
var timer: Timer!
func start(from seconds: Int){
self.secondsLeft = seconds
print("start timer")
self.timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
// don't know what Block is
self.secondsLeft -= 1
print("Seconds left: \(self.secondsLeft)")
}
}
func stop(){
self.timer?.invalidate()
}
}
struct ContentView: View {
#ObservedObject var countdown = CountDown()
var body: some View {
Text("\(self.countdown.secondsLeft)")
.font(.system(size: 30))
.foregroundColor(.white)
.frame(width: 100, height: 40)
.padding(EdgeInsets(top: 5, leading: 15, bottom: 5, trailing: 15))
.background(
Capsule()
.fill(Color(.black)) // don't know what Exit is
.opacity(0.7)
)
.onAppear{
print("Start countdown")
self.countdown.start(from: 30)
}
.onDisappear {
self.countdown.stop()
}
}
}
Copy and paste that into a new SwiftUI project and you'll see that the text does count down.
So if something is preventing that from happening, it's something you didn't show.

Related

My system has run out of application memory(app)

This problem appeared when i wrote Text("Today i made:"............), 68 line and i don't know how to fix it.
My thought, that probably problem with #AppStorage(in HomeView i have same variable.... var savedSalary = Double()). But in this View i decided to write #AppStorage with different variable, i just added one more "y" in the end and wrote(var savedSalaryy: Double = 8.75).
Restart didn't help me too.
Before the reading the code watch this screen
import SwiftUI
struct DayOnWorkView: View {
#State private var ifTapped = false
#State private var startTime = Date()
#State private var timerInt = 0 // was string
#AppStorage("SALARY_KEY") var savedSalaryy: Double = 8.75 // was savedSalary = Double()
let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
var body: some View {
NavigationView {
ZStack {
BackgroundView()
VStack(alignment: .center) {
Button(action: {
self.ifTapped.toggle()
}, label: {
Image(systemName: ifTapped ? "person.crop.circle.badge.checkmark" : "person.crop.circle.badge.xmark")
.resizable()
.scaledToFit()
.frame(width: 40, height: 40)
.onTapGesture {
if !ifTapped {
timerInt = 0
startTime = Date()
}
ifTapped.toggle()
}
Text(ifTapped ? "Online" : "Offline")
.font(.system(size: 30, design: .rounded))
.onTapGesture {
if !ifTapped {
timerInt = 0
startTime = Date()
}
ifTapped.toggle()
}
})
.tint(ifTapped ? Color.green : Color.red)
.shadow(radius: 30)
.opacity(0.9)
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.offset(y: 150)
}
VStack {
// timer
Text(String(timerInt))
.font(Font.system(.largeTitle, design: .monospaced))
.foregroundColor(Color.white)
.onReceive(timer) { _ in
if self.ifTapped {
timerInt = Int(Date().timeIntervalSince( self.startTime))
}
}
Text("Today i made: \(Double(Double(savedSalaryy) * Double(Int(timerInt))) / 3600, specifier: "%.2f")")
.offset(y: 150)
.foregroundColor(Color.white)
.bold()
}
}
}
}
}
// Text("Today i made \(Double(Double(savedSalaryy) * Double(Int(timerInt))) / 3600)") WORK WORK WORK
struct DayOnWorkView_Previews: PreviewProvider {
static var previews: some View {
DayOnWorkView()
}
}

SwiftUI Custom Half Modal Calculator Not Working

I'm working on a math game and I would like to implement a small calculator in the background with a custom half modal.
The problem is that the inputted number is not updated unless you physically dismiss the modal and open it again. Although this is an extremely stressful calculator to use, I want to have the text updated immediately after a button is pressed just like a normal calculator (except the fact that it is in a half modal but still).
Please let me know if there is something I'm missing anything and here's my code to review...
struct PGQuestion1: View {
#State var show = false
#State var showA = false
#State var showB = false
#State var showC = false
#State var showSheet: Bool = false
#State var showSheetA: Bool = false
#State var showSheet2: Bool = false
#State private var chromaShift = false
#State var value = "0"
let buttons: [[CalcButton]] = [
[.clear, .negative, .percent, .divide],
[.seven, .eight, .nine, .multiply],
[.four, .five, .six, .subtract],
[.one, .two, .three, .add],
[.zero, .decimal, .equal]
]
#State var currentOperation: Operation = .none
#State var runningNumber = 0
var body: some View {
ZStack {
Color.indigo.ignoresSafeArea()
VStack {
VStack(alignment: .leading) {
HStack {
Image(systemName: "1.circle")
.foregroundColor(.white)
.font(.largeTitle)
.padding()
Spacer()
}
}
VStack {
Text("Solve the following:")
.foregroundColor(.white)
.font(.title2)
.fontWeight(.medium)
.padding(.bottom, 44)
Text("(54-9)÷5")
.foregroundColor(.white)
.font(.system(size: 60, design: .rounded))
.fontWeight(.medium)
.padding(.bottom, 45)
VStack(spacing: 0.01) {
ZStack {
VStack {
if showA {
Rectangle()
.fill(Color.purple.opacity(0.95))
.frame(maxWidth: .infinity, maxHeight: 100)
.cornerRadius(30)
.padding()
} else {
Rectangle()
.fill(Color.white.opacity(0.5))
.frame(maxWidth: .infinity, maxHeight: 100)
.cornerRadius(30)
.padding()
}
}
.onTapGesture {
showA.toggle()
self.showB = false
self.showC = false
}
Text("9")
.font(.largeTitle)
.foregroundColor(.white)
}
ZStack {
VStack {
if showB {
Rectangle()
.fill(Color.purple.opacity(0.95))
.frame(maxWidth: .infinity, maxHeight: 100)
.cornerRadius(30)
.padding()
} else {
Rectangle()
.fill(Color.white.opacity(0.5))
.frame(maxWidth: .infinity, maxHeight: 100)
.cornerRadius(30)
.padding()
}
}
.onTapGesture {
showB.toggle()
self.showA = false
self.showC = false
}
Text("52.2")
.font(.largeTitle)
.foregroundColor(.white)
}
HStack(spacing: 20) {
Button {
showSheet.toggle()
} label: {
VStack {
ZStack {
Circle()
.fill(Color.purple.opacity(0.8))
.frame(width: 80, height: 80)
.padding(.top, 13)
.padding(.leading, 28.5)
Image(systemName: "circle.grid.3x3.fill")
.foregroundColor(.white)
.font(.largeTitle)
.padding(.top, 13)
.padding(.leading, 28.5)
}
Text("Calculator")
.foregroundColor(.white)
.padding(.bottom)
.padding(.leading, 28.5)
}
}
.halfSheet(showSheet: $showSheet) {
ZStack {
Color.black.opacity(0.9).ignoresSafeArea()
VStack {
Spacer()
// Text display
HStack {
Spacer()
Text(value)
.bold()
.font(.system(size: 70))
.foregroundColor(.white)
.minimumScaleFactor(0.5)
}
.padding(.leading)
.padding([.top, .trailing], 23)
.padding(.bottom, 2)
// Our Buttons
ForEach(buttons, id: \.self) { row in
HStack(spacing: 12) {
ForEach(row, id: \.self) { item in
Button {
self.didTap(button: item)
} label: {
Text(item.rawValue)
.font(.system(size: 36))
.frame(width: self.buttonWidth(item: item), height: 55)
.scaledToFit()
.background(item.buttonColor)
.foregroundColor(.white)
.cornerRadius(95)
}
}
}
.padding(.bottom, 0.55)
.padding([.leading, .trailing], 20)
}
}
}
}
Button {
showSheet2.toggle()
} label: {
VStack {
ZStack {
Circle()
.fill(Color.purple.opacity(0.8))
.frame(width: 80, height: 80)
.padding(.top, 13)
Image(systemName: "book")
.foregroundColor(.white)
.font(.largeTitle)
.padding(.top, 13)
}
Text("Dictonary")
.foregroundColor(.white)
.padding(.bottom)
}
}
.halfSheet(showSheet: $showSheet2) {
ZStack {
Color.black.opacity(0.9).ignoresSafeArea()
VStack(alignment: .leading) {
HStack {
}
}
}
}
VStack {
if !show {
ZStack {
Circle()
.fill(Color.purple.opacity(0.8))
.frame(width: 80, height: 80)
.blur(radius: 10)
.padding(.top, 13)
.padding(.trailing)
Image(systemName: "info")
.foregroundColor(.black)
.font(.largeTitle)
.padding(.top, 13)
.padding(.trailing)
}
.onTapGesture {
withAnimation {
show.toggle()
}
}
} else {
ZStack {
Circle()
.fill(Color.purple.opacity(0.8))
.frame(width: 80, height: 80)
.blur(radius: 10.35)
.padding(.top, 13)
.padding(.trailing)
Text("Upgrade to Mathematically MAX")
.foregroundColor(.black)
.font(.system(size: 13))
.minimumScaleFactor(0.5)
.multilineTextAlignment(.center)
.padding(.top, 16)
.padding(1)
.padding(4)
.padding(5)
.padding(.trailing)
}
.onTapGesture {
withAnimation {
show.toggle()
}
}
}
Text("Intelligent Finding")
.foregroundColor(.black)
.font(.system(size: 12))
.padding(.top, 2)
.padding(.bottom)
.padding(.trailing)
}
}
.frame(maxWidth: .infinity, maxHeight: 140)
.background(Color.white.opacity(0.3))
.cornerRadius(40)
.padding()
}
}
HStack {
ZStack {
Text("Swipe to the next page")
.font(.title2)
.foregroundColor(Color.yellow)
.shadow(color: .white, radius: 10)
.hueRotation(.degrees(chromaShift ? 0 : 520))
.animation(Animation.linear(duration: 4).repeatForever(autoreverses: true))
.onAppear() {
self.chromaShift.toggle()
}
}
Image(systemName: "chevron.forward")
.font(.title2)
.foregroundColor(Color.yellow)
.shadow(color: .white, radius: 10)
.hueRotation(.degrees(chromaShift ? 0 : 520))
.animation(Animation.linear(duration: 4).repeatForever(autoreverses: true))
.onAppear() {
self.chromaShift.toggle()
}
}
Spacer()
}
}
}
func didTap(button: CalcButton) {
switch button {
case .add, .subtract, .multiply, .divide, .equal:
if button == .add {
self.currentOperation = .add
self.runningNumber = Int(self.value) ?? 0
} else if button == .subtract {
self.currentOperation = .subtract
self.runningNumber = Int(self.value) ?? 0
} else if button == .multiply {
self.currentOperation = .multiply
self.runningNumber = Int(self.value) ?? 0
} else if button == .divide {
self.currentOperation = .divide
self.runningNumber = Int(self.value) ?? 0
} else if button == .equal {
let runningValue = self.runningNumber
let currentValue = Int(self.value) ?? 0
switch self.currentOperation {
case .add:
self.value = "\(runningValue + currentValue)"
case .subtract:
self.value = "\(runningValue - currentValue)"
case .multiply:
self.value = "\(runningValue * currentValue)"
case .divide:
self.value = "\(runningValue / currentValue)"
case .none:
break
}
}
if button != .equal {
self.value = "0"
}
case .clear:
self.value = "0"
case .decimal, .percent, .negative:
break
default:
let number = button.rawValue
if self.value == "0" {
value = number
} else {
self.value = "\(self.value)\(number)"
}
}
}
func buttonWidth(item: CalcButton) -> CGFloat {
if item == .zero {
return ((UIScreen.main.bounds.width - (4*12)) / 4) * 2
}
return (UIScreen.main.bounds.width - (5*12)) / 4
}
func buttonHeight() -> CGFloat {
return (UIScreen.main.bounds.height - (5*12)) / 4
}
}
enum CalcButton: String {
case one = "1"
case two = "2"
case three = "3"
case four = "4"
case five = "5"
case six = "6"
case seven = "7"
case eight = "8"
case nine = "9"
case zero = "0"
case add = "+"
case subtract = "-"
case divide = "÷"
case multiply = "×"
case equal = "="
case clear = "AC"
case decimal = "."
case percent = "%"
case negative = "+/-"
var buttonColor: Color {
switch self {
case .add, .subtract, .multiply, .divide, .equal:
return .orange
case .clear, .negative, .percent:
return .gray
default:
return Color(UIColor(red: 55/255.0, green: 55/255.0, blue: 55/255.0, alpha: 1))
}
}
}
enum Operation {
case add, subtract, multiply, divide, none
}
extension View {
func halfSheet<SheetView: View>(showSheet: Binding<Bool>, #ViewBuilder sheetView: #escaping () -> SheetView) -> some View {
return self
.background(
HalfSheetHelper(sheetView: sheetView(), showSheet: showSheet)
)
}
}
struct HalfSheetHelper<SheetView: View>: UIViewControllerRepresentable {
var sheetView: SheetView
#Binding var showSheet: Bool
let controller = UIViewController()
func makeUIViewController(context: Context) -> UIViewController {
controller.view.backgroundColor = .clear
return controller
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
let sheetController = CustomHostingController(rootView: sheetView)
if showSheet {
uiViewController.present(sheetController, animated: true) {
DispatchQueue.main.async {
self.showSheet.toggle()
}
}
}
}
}
class CustomHostingController<Content: View>: UIHostingController<Content> {
override func viewDidLoad() {
if let presentationController = presentationController as? UISheetPresentationController {
presentationController.detents = [
.medium(),
.large()
]
presentationController.prefersGrabberVisible = true
}
}
}
The problem is in your func halfSheet() and struct HalfSheetHelper. You have created a UIViewControllerRepresentable view, but it does not handle updates.
Once the sheet has already been shown, the sequence of events happening is:
Pressing a button triggers a change to self.value inside PGQuestion1.
SwiftUI re-renders PGQuestion1, and a new closure is passed to .halfSheet { ... } which uses the new value.
Since the HalfSheetHelper is UIViewRepresentable, SwiftUI calls your updateUIViewController() function. A completely new CustomHostingController is created. Then nothing else happens because showSheet is false.
To handle updates properly, I recommend you create a Coordinator class inside your HalfSheetHelper. The coordinator is a class that SwiftUI will keep alive as long as the view is being used, and it can maintain a persistent reference to the CustomHostingController. Then in updateUIViewController(), you can use the coordinator to access the hosting controller and re-assign its rootView with the newly updated sheet contents. For more on these techniques, see the Interfacing with UIKit tutorial.
I also changed how showSheet is handled so that it becomes false only once the sheet is dismissed. This required adding a custom onDismiss closure to the CustomHostingController which it calls in viewDidDisappear.
(There still seems to be a bug with the half-sheet, where if I swipe down to dismiss the sheet and then show it again, it appears fullscreen instead of half-screen. I'm not familiar enough with the presentationController/detents APIs to figure out why this is happening!)
struct HalfSheetHelper<SheetView: View>: UIViewControllerRepresentable {
var sheetView: SheetView
#Binding var showSheet: Bool
class Coordinator {
let dummyController = UIViewController()
let sheetController: CustomHostingController<SheetView>
init(sheetView: SheetView, showSheet: Binding<Bool>) {
sheetController = CustomHostingController(rootView: sheetView, onDismiss: { showSheet.wrappedValue = false })
dummyController.view.backgroundColor = .clear
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(sheetView: sheetView, showSheet: $showSheet)
}
func makeUIViewController(context: Context) -> UIViewController {
return context.coordinator.dummyController
}
func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
context.coordinator.sheetController.rootView = sheetView
if showSheet && uiViewController.presentedViewController == nil {
uiViewController.present(context.coordinator.sheetController, animated: true)
}
}
}
class CustomHostingController<Content: View>: UIHostingController<Content> {
var onDismiss: (() -> Void)?
convenience init(rootView: Content, onDismiss: #escaping () -> Void) {
self.init(rootView: rootView)
self.onDismiss = onDismiss
}
override func viewDidLoad() {
if let presentationController = presentationController as? UISheetPresentationController {
presentationController.detents = [
.medium(),
.large()
]
presentationController.prefersGrabberVisible = true
}
}
override func viewDidDisappear(_ animated: Bool) {
onDismiss?()
}
}

Very weird bug where sheet cover is not working in Swift iOS 14.1-ios 14.4 but working for iOS 14.5 and above

this bug has been scratching my head for the past few days and I still don't know why the problem is arising and what the fix is. I have a camera screen and integrated it with the TOCropViewController (https://github.com/TimOliver/TOCropViewController) to allow a user to select a picture from their photo library and crop it to show a new post. For some reason the image picker is detecting that it should change the view to the ImagePicker from the camera view screen but it's not displaying it on ios14.4 and below but it works just fine for iOS 14.5 and above.
Here is my camera view code:
struct CameraView: View {
#StateObject var model = CameraModel()
#State var currentZoomFactor: CGFloat = 1.0
#StateObject var registerData = RegisterViewModel()
#StateObject var newPostData = NewPostModel()
enum SheetType {
case imagePick
case imageCrop
case share
}
#State private var currentSheet: SheetType = .imagePick
#State private var actionSheetIsPresented = false
#State private var sheetIsPresented = false
#State private var originalImage: UIImage?
#State private var image: UIImage?
#State private var croppingStyle = CropViewCroppingStyle.default
#State private var croppedRect = CGRect.zero
#State private var croppedAngle = 0
#StateObject var userData = UserViewModel()
var captureButton: some View {
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
model.capturePhoto()
}, label: {
Circle()
.foregroundColor(.white)
.frame(width: 80, height: 80, alignment: .center)
.overlay(
Circle()
.stroke(Color.black.opacity(0.8), lineWidth: 2)
.frame(width: 65, height: 65, alignment: .center)
)
})
}
var capturedPhotoThumbnail: some View {
Group {
RoundedRectangle(cornerRadius: 10)
.frame(width: 55, height: 55, alignment: .center)
.foregroundColor(Color.gray.opacity(0.2))
.onTapGesture(perform: {
// newPostData.picker.toggle()
self.croppingStyle = .default
self.currentSheet = .imagePick
self.sheetIsPresented = true
print("HERE11 and \(self.currentSheet) and \(self.sheetIsPresented)")
})
.overlay(
Image("gallery")
.renderingMode(.template)
.resizable()
.frame(width: 25, height: 25)
.foregroundColor(Color("white")))
//CODE WITH BUG on ios 14.4 and below. I tried a regular sheet as well that works on another view in ios 14.4 but it doesn't work in the cameraview()
.sheet(isPresented: $sheetIsPresented) {
if (self.currentSheet == .imagePick) {
ImagePickerView(croppingStyle: self.croppingStyle, sourceType: .photoLibrary, onCanceled: {
// on cancel
}) { (image) in
guard let image = image else {
return
}
self.originalImage = image
DispatchQueue.main.async {
self.currentSheet = .imageCrop
self.sheetIsPresented = true
}
}
} else if (self.currentSheet == .imageCrop) {
ZStack {
Color("imagecropcolor").edgesIgnoringSafeArea(.all)
ImageCropView(croppingStyle: self.croppingStyle, originalImage: self.originalImage!, onCanceled: {
// on cancel
}) { (image, cropRect, angle) in
// on success
self.image = image
model.resetPhoto()
newPostData.newPost.toggle()
}
}
}
}
}
}
var flipCameraButton: some View {
Button(action: {
let impactMed = UIImpactFeedbackGenerator(style: .light)
impactMed.impactOccurred()
model.flipCamera()
}, label: {
Circle()
.foregroundColor(Color.gray.opacity(0.2))
.frame(width: 45, height: 45, alignment: .center)
.overlay(
Image(systemName: "camera.rotate.fill")
.foregroundColor(.white))
})
}
var body: some View {
GeometryReader { reader in
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
VStack {
HStack{
Button(action: {
model.switchFlash()
}, label: {
Image(systemName: model.isFlashOn ? "bolt.fill" : "bolt.slash.fill")
.font(.system(size: 20, weight: .medium, design: .default))
})
.accentColor(model.isFlashOn ? .yellow : .white)
.padding(.leading, 30)
Spacer()
if model.photo != nil {
Text("taken photo").onAppear{
newPostData.newPost.toggle()
}
}
// Image(uiImage: model.photo.image!)
// .resizable()
// .aspectRatio(contentMode: .fill)
// .frame(width: 60, height: 60)
// .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
// .animation(.spring())
//
}
CameraPreview(session: model.session)
.gesture(
DragGesture().onChanged({ (val) in
// Only accept vertical drag
if abs(val.translation.height) > abs(val.translation.width) {
// Get the percentage of vertical screen space covered by drag
let percentage: CGFloat = -(val.translation.height / reader.size.height)
// Calculate new zoom factor
let calc = currentZoomFactor + percentage
// Limit zoom factor to a maximum of 5x and a minimum of 1x
let zoomFactor: CGFloat = min(max(calc, 1), 5)
// Store the newly calculated zoom factor
currentZoomFactor = zoomFactor
// Sets the zoom factor to the capture device session
model.zoom(with: zoomFactor)
}
})
)
.onAppear {
model.configure()
}
.alert(isPresented: $model.showAlertError, content: {
Alert(title: Text(model.alertError.title), message: Text(model.alertError.message), dismissButton: .default(Text(model.alertError.primaryButtonTitle), action: {
model.alertError.primaryAction?()
}))
})
.overlay(
Group {
if model.willCapturePhoto {
Color.black
}
}
)
.animation(.easeInOut)
HStack {
capturedPhotoThumbnail
Spacer()
captureButton
.padding(.trailing, 10)
Spacer()
flipCameraButton
}
.padding(.horizontal, 20)
.padding(.bottom, 20)
}
}.fullScreenCover(isPresented: $newPostData.newPost) {
if model.photo == nil {
NewPost(imageData: (self.image?.pngData())! )
} else {
NewPost(imageData: model.photo.originalData)
}
}
}
}
}
Here is where the CameraView() gets called from my Home Screen
import SwiftUI
import Firebase
struct Home: View {
#AppStorage("current_status") var status = false
#AppStorage("showSheet") var showSheet = false
#State var loadedPost = Post(id: 0, PostUID: "", PostName: "", selectedForPost: false, time: Date())
#State var selectedTab = "camera"
var edges = UIApplication.shared.windows.first?.safeAreaInsets
#StateObject var modelData = ModelView()
#StateObject var userData = UserViewModel()
var body: some View {
VStack(spacing: 15){
VStack (spacing: 0) {
GeometryReader{_ in
ZStack{
if selectedTab == "Post"{
Post(loadedPost: $loadedPost, selectedTab: $selectedTab)
}else if selectedTab == "camera"{
CameraView()
}else if selectedTab == "user"{
User(selectedTab: $selectedTab, loadedPost: $loadedPost)
}
}
}.onChange(of: selectedTab) { (_) in
switch(selectedTab){
case "Post": if
!modelData.isPostLoad{modelData.loadPost()}
case "camera": if
!modelData.isCameraLoad{modelData.loadCamera()}
case "user": if
!modelData.isUserLoad{modelData.loadUser()}
default: ()
}
}
//Tabview hide to show friend modal
if !showSheet{
Divider()
HStack(spacing: 0) {
Spacer(minLength: 0)
TabButton(title: "Post", selectedTab: $selectedTab)
Spacer(minLength: 0)
TabButton(title: "camera", selectedTab: $selectedTab)
.padding(.leading, 30)
.padding(.trailing, 30)
Spacer(minLength: 0)
TabButton(title: "user", selectedTab: $selectedTab)
Spacer(minLength: 0)
}
.padding(.horizontal, 30)
.padding(.bottom, edges!.bottom == 0 ? 15 : edges!.bottom)
.background(Color.black)
}
}
.ignoresSafeArea(.all, edges: .bottom)
.background(Color("Black").ignoresSafeArea(.all, edges: .all))
}
}
}
//Tab Button
struct TabButton : View {
var title: String
#Binding var selectedTab: String
var body: some View {
Button(action: {
withAnimation{selectedTab = title}
}) {
VStack(spacing: 5) {
//Top indicator
//Custom shape...
if title == "user" {
Image(title)
.renderingMode(.template)
.resizable()
.foregroundColor(selectedTab == title ? Color.white : Color("Grey"))
.frame(width: 26.5, height: 26.5)
.padding(.top, UIScreen.screenHeight < 500 ? -5 : 15)
}else if title == "camera"{
Image(title)
.renderingMode(.template)
.resizable()
.foregroundColor(selectedTab == title ? Color.white : Color("Grey"))
.frame(width: 40, height: 40)
.padding(.top, UIScreen.screenHeight < 500 ? -5 : 15)
}else{
Image(title)
.renderingMode(.template)
.resizable()
.foregroundColor(selectedTab == title ? Color.white : Color("Grey"))
.frame(width: 32.5, height: 32.5)
.padding(.top, UIScreen.screenHeight < 500 ? -5 : 15)
}
}
}
}
}
//can update with load views here
class ModelView: ObservableObject {
#Published var isPostLoad = false
#Published var isCameraLoad = false
#Published var isUserLoad = false
init() {
//load initial data
isCameraLoad = true
print("Home Data Loaded")
}
func loadPost(){
print("Post Loaded")
isPostLoad = true
}
func loadCamera(){
print("Camera Loaded")
isCameraLoad = true
}
func loadUser(){
print("User loaded")
isUserLoad = true
}
}
I would greatly appreciate any help on how to get the ImagePicker view to show up for iOS 14.1-ios 14.4 I've been scratching my head since I worked on it assuming anything that works on iOS 14.5 and above should work on below but only this specific ImagePicker is not working as intended. Thanks!

SwiftUI custom modal view transition lagging

I have a custom modal View which is part of the ZStack which overlays the other content when enabled.
When a button is pressed, I want the modal sheet to transition from the bottom edge of the device to the centre of the screen, which I have somewhat accomplished. However, the animation somewhat fails when dismissing the modal view, as seen in the provided video, and I'm having difficulties figuring out why this is.
The animation of the modal view I'm using is
.animation(Animation.spring().speed(1.5))
.transition(.move(edge: .bottom))
For the sake of completion, here is my modal view:
struct AddEventView: View {
#State var eventName: String = ""
#State var endDate = Date().addingTimeInterval(60)
#State var gradientIndex: Int = 0
#EnvironmentObject var model: Model
let existingEvent: Event?
let linearGradients: [LinearGradient] = Gradient.gradients.map {
LinearGradient(
gradient: $0,
startPoint: .topTrailing,
endPoint: .bottomLeading
)
}
/// This closure is invoked when the view is dimissed, with a newly created Event passed as its parameter.
/// If the user cancelled this action, `nil` is passed as the parameter
let onDismiss: (Event?) -> Void
var body: some View {
print("Redrawing AddEventView")
return VStack(spacing: 30.0) {
HStack {
Spacer().frame(width: 44)
Spacer()
Text(existingEvent == nil ? "Create Event" : "Edit Event")
.font(.title3)
.bold()
Spacer()
Button(action: {
onDismiss(nil)
}) {
Image(systemName: "xmark.circle.fill")
.imageScale(.large)
}
.frame(width: 44)
}
.padding(.bottom, 5)
.padding(.top, 8)
HStack {
Text("Name of Event").padding(.trailing, 20)
TextField("My Birthday", text: $eventName)
.frame(height: 35)
}
DatePicker(
"Date of Event".padding(toLength: 19, withPad: " ", startingAt: 0),
selection: $endDate,
in: Date()...
)
.frame(height: 35)
ColorChooser(
linearGradients,
selectedIndex: $gradientIndex
)
.frame(height: 75)
Button(action: {
let adjustedEnd = Calendar.current.date(bySetting: .second, value: 0, of: endDate)
let event = Event(
name: eventName,
start: existingEvent?.start ?? Date(),
end: adjustedEnd!,
gradientIndex: gradientIndex
)
onDismiss(event)
}) {
RoundedRectangle(cornerRadius: 13)
.frame(maxWidth: .infinity)
.frame(height: 42)
.overlay(
Text(existingEvent == nil ? "Add Event" : "Edit Event")
.foregroundColor(.white)
.bold()
)
.padding(.horizontal, 1)
}
.padding(.top, 8)
.disabled(self.eventName.isEmpty)
}
.padding(.all, 16)
.background(Color.white)
.cornerRadius(16)
.shadow(radius: 16)
.onAppear {
if let event = existingEvent {
self.eventName = event.name
self.endDate = event.end
self.gradientIndex = event.gradientIndex
}
}
}
}
and my ContentView:
struct ContentView: View {
#State var progress: Double = 0.0
#State var showModal: Bool = false
#State var showPopover: Bool = false
#State var modifiableEvent: Event?
#State var now: Date = Date()
#State var confettiView = ConfettiUIView()
#EnvironmentObject var model: Model
let timer = Timer.publish(every: 1, on: .current, in: .common).autoconnect()
let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 10), count: 2)
var alertButtons: [Alert.Button] {
return Model.SortableKeyPaths.map { key, _ in
.default(Text(key)) { model.sortedKey = key }
}
}
func onEventEnd() {
self.confettiView.emit(with: [.text("🎉")])
AudioManager.shared.play("Success 1.mp4")
let taptics = UINotificationFeedbackGenerator()
taptics.notificationOccurred(.success)
}
var grid: some View {
LazyVGrid(columns: columns, spacing: 10) {
ForEach(model.events, id: \.self) { event in
SmallCardView(event: event)
.contextMenu {
Button(action: {
modifiableEvent = event
withAnimation {
self.showModal = true
}
}) {
Text("Edit")
Image(systemName: "slider.horizontal.3")
}
Button(action: {
model.removeEvent(event)
}) {
Text("Delete")
Image(systemName: "trash")
}
}
.animation(.linear)
}
if !showModal || modifiableEvent != nil {
AddEventButtonView() {
modifiableEvent = nil
self.showModal = true
}
} else {
Spacer().frame(height: 100)
}
}
.navigationBarTitle(Text("My Events"), displayMode: .large)
.navigationBarItems(
leading: Button(action: { }) {
Image(systemName: "ellipsis")
.imageScale(.large)
},
trailing: Button(action: { self.showPopover = true }) {
Image(systemName: "arrow.up.arrow.down").imageScale(.large)
}
.actionSheet(isPresented: $showPopover) {
ActionSheet(
title: Text("Sort Events"),
buttons: alertButtons + [.cancel()]
)
}
)
}
var body: some View {
return ZStack {
NavigationView {
ScrollView {
grid.padding(.horizontal, 16)
}
.padding(.top)
}
.brightness(self.showModal ? -0.1 : 0)
.blur(radius: self.showModal ? 16 : 0)
.scaleEffect(self.showModal ? 0.95 : 1)
if self.showModal {
AddEventView(existingEvent: modifiableEvent) { event in
if let event = event {
self.model.removeEvent(modifiableEvent)
self.model.addEvent(event)
}
withAnimation {
self.showModal = false
}
}
.padding(.horizontal, 16)
.zIndex(1.0)
.animation(Animation.spring().speed(1.5))
.transition(.move(edge: .bottom))
}
EmptyView().id("\(self.now.hashValue)")
}
.overlay(
UIViewWrapper(view: $confettiView)
.edgesIgnoringSafeArea(.all)
.allowsHitTesting(false)
)
.onReceive(timer) { _ in
if !showModal { self.now = Date() }
if model.events.contains(where: { -1...0 ~= $0.timeRemaining }) {
onEventEnd()
}
}
}
}

Loading data in tabview

I created a tabview with 4 views. Each view has a viewModel where I load the data needed in the view from ana api. It works fine except the first view is always empty unless I tap in a tab then tap on the first view tap again.
Any idea how I could make sure all the first view's data is there as soon as it's loaded?
Find my code below
thanks
-Contentview
#ViewBuilder
var body: some View {
if isLoggedIn() {
MainScreen()
} else {
UnAuthenticatedScreen()
}
}
-MainView
#ObservedObject var eventsVM: EventsVM = EventsVM()
var body: some View {
TabView(){
HomeScreen(events: self.eventsVM.events)
.tabItem {
Image(systemName: "house")
Text("Home")
}
.navigationBarHidden(true)
EventsScreen(events: self.eventsVM.events)
.tabItem {
Image(systemName: "calendar")
Text("Events")
}.navigationBarHidden(true)
}
}
- EventsVM
import Foundation
import Combine
class EventsVM: ObservableObject {
let didChange = PassthroughSubject<[EventModel], Never>()
private let eventsService: EventService
#Published var events = [EventModel]()
init() {
self.eventsService = EventService()
self.fetchEvents()
}
private func fetchEvents(){
self.eventsService.getAllEvents { (_events, _error) in
guard let events = _events else { return }
self.events = events
}
}
}
The home view
import SwiftUI
struct HomeScreen: View {
var eventsVM: EventsVM
#State var news: [NewsModel] = []
#State var albums = [AlbumModel]()
init(eventsVM: EventsVM){
self.eventsVM = EventsVM()
}
var body: some View {
GeometryReader { gr in
VStack(alignment: .leading, spacing: 0) {
HStack{
Spacer()
}
HStack {
Spacer()
Text("About Us")
Image("logo_squad")
.resizable()
.frame(width: 50, height: 50)
}
Text("Events").font(Font.custom("Francois One", size: 30)).foregroundColor(.red)
ScrollView(.horizontal, showsIndicators: false){
HStack {
ForEach(self.eventsVM.events, id: \.self) { event in
HomeEventRow(event: event).frame(width: gr.size.width - 60, height: 170)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 1).shadow(radius: -2)
)
.clipShape(RoundedRectangle(cornerRadius: 10))
}
}
}
// Text("News").font(Font.custom("Francois One", size: 30)).foregroundColor(.red)
// ScrollView(.horizontal, showsIndicators: false){
// HStack {
// ForEach(self.news, id: \.self){ _news in
// HomeNewsRow(news: _news)
// .frame(width: gr.size.width - 60, height: 150)
// .overlay(
// RoundedRectangle(cornerRadius: 10)
// .stroke(Color.gray, lineWidth: 1).shadow(radius: -2)
// )
// .clipShape(RoundedRectangle(cornerRadius: 10))
// }
// }
// }
// Text("Albums").font(Font.custom("Francois One", size: 30)).foregroundColor(.red)
// ScrollView(Axis.Set.horizontal, showsIndicators: true){
// HStack {
// ForEach(self.albums, id: \.self){ album in
// HomeMediaRow(album: album)
// .frame(width: gr.size.width - 60, height: 150)
// }.overlay(
// RoundedRectangle(cornerRadius: 10)
// .stroke(Color.gray, lineWidth: 1).shadow(radius: -2)
// )
// .clipShape(RoundedRectangle(cornerRadius: 10))
// }
// }
Spacer()
}
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
.onAppear {
print("self.eventsVM.events \(self.eventsVM.events)")
}
}.padding(EdgeInsets(top: 0, leading: 5, bottom: 0, trailing: 0))
}
}
struct HomeScreen_Previews: PreviewProvider {
static var previews: some View {
HomeScreen(eventsVM: EventsVM())
}
}

Resources