#Published ObservedObjects SwiftUI Updates not Happening - xcode

#Published ObservedObjects SwiftUI Updates not Happening
I have created a very basic ObservableObject app and some bindings are updated
correctly and some are not. I must be missing something simple. Two views only - the
start view and a second view. I only use Text, Button and Toggle - just to test
this concept.
I want to store startup property values in UserDefaults. That part seems to work
fine. When I make changes on the second page, they are updated on that page, and the
UserDefaults are correctly written. However, on returning to the start view, the
Published bindings are not updated. Interestingly, the UserDefaults are updated.
The main view simply displays the values of the bindings and user defaults.
The start view:
import SwiftUI
struct ContentView: View {
#State private var showUtilities = false
#ObservedObject var userDefaultManager = UserDefaultsManager()
var body: some View {
NavigationView {
VStack{
Group { //group 1
Text("Published userDefaultManager.name:")
Text("\(UserUtilities().makeSubString(stringIn: self.userDefaultManager.name, count: 24))")
.padding(.bottom, 30)
.lineLimit(0)
Text("UserDefaults.standard.value:")
Text("\(UserUtilities().makeSubString(stringIn: UserDefaults.standard.value(forKey: "name") as! String, count: 24))")
.padding(.bottom, 30)
.lineLimit(0)
Text("Published userDefaultsManager.enableBiometrics:")
Text(String(self.userDefaultManager.enableBiometrics))
.padding(.bottom, 30)
.font(.headline)
} //group 1
Group { //group 2
Text("UserDefaults.standard.bool:")
Text(String(UserDefaults.standard.bool(forKey: AppDelegate.eb)))
.padding(.bottom, 30)
.font(.headline)
Button(action: {
self.showUtilities.toggle()
}) {
Text("Show Utilities")
}
.frame(width: 200)
.padding()
.font(.headline)
}//group 2
}//vstack
.navigationBarTitle("Read the Values")
.sheet(isPresented: $showUtilities) {
UserUtilities()
}
}
}
}
The second view:
import SwiftUI
import Combine
struct UserUtilities: View {
#ObservedObject var userDefaultManager = UserDefaultsManager()
var body: some View {
NavigationView {
VStack {
Toggle(isOn: self.$userDefaultManager.enableBiometrics) {
Text("Enable Biometric Login")
}
.padding(EdgeInsets(top: 50, leading: 50, bottom: 30, trailing: 50))
//this does not update
Text("Published name is \(UserUtilities().makeSubString(stringIn: self.userDefaultManager.name, count: 24))")
.padding(EdgeInsets(top: 0, leading: 0, bottom: 30, trailing: 0))
Text("UserDefaults name is \(UserUtilities().makeSubString(stringIn: UserDefaults.standard.value(forKey: "name") as! String, count: 24))")
.padding(EdgeInsets(top: 0, leading: 0, bottom: 30, trailing: 0))
\\this does not update
Text("Published enableBiometrics is " + String(self.userDefaultManager.enableBiometrics) )
Text("UserDefaults is " + String(UserDefaults.standard.bool(forKey: AppDelegate.eb)) )
.padding(.bottom, 20)
Button(action: {
self.userDefaultManager.name = UUID().uuidString
}) {
Text("Change the Published name")
}
.padding()
.font(.headline)
}
.navigationBarTitle("User Utilities", displayMode: .inline)
}
}//body
func makeSubString(stringIn: String, count: Int) -> String {
if stringIn.count > count {
let modString = stringIn.dropFirst(count)
let returnString = String(modString)
return returnString
}
return "can't get substring"
}
}
My UserDefault Manager:
import SwiftUI
import Combine
class UserDefaultsManager: ObservableObject {
#Published var name = UserDefaults.standard.value(forKey: "name") as! String {
didSet {
UserDefaults.standard.set(self.name, forKey: "name")
}
}
#Published var enableBiometrics: Bool = UserDefaults.standard.bool(forKey: AppDelegate.eb) {
didSet {
UserDefaults.standard.set(self.enableBiometrics, forKey: AppDelegate.eb)
}
}
}
And for completeness - in the AppDelegate:
static let eb = "enablebiometrics"
#ObservedObject var userDefaultManager = UserDefaultsManager()
In didFinishLaunchingWithOptions:
if UserDefaults.standard.value(forKey: AppDelegate.eb) == nil {
UserDefaults.standard.set(false, forKey: AppDelegate.eb)
}
userDefaultManager.enableBiometrics = UserDefaults.standard.bool(forKey: AppDelegate.eb)
if UserDefaults.standard.value(forKey: "name") == nil {
UserDefaults.standard.set("set in AppDelegate", forKey: "name")
}
userDefaultManager.name = UserDefaults.standard.value(forKey: "name") as! String
Any guidance would be appreciated. Xcode 11.3 (11C29)

You are using 2 Instances of your UserDefaultsManager. Instantiate it only one and pass it to the second view or add it to the environment (#EnvironmentObject) to access it in all the views.
A good example how to to this you can find at https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views.

In UserUtilities, change #ObservedObject var userDefaultManager = UserDefaultsManager() to #Binding var userDefaultManager: UserDefaultsManager, and pass the userDefaultManager variable in ContentView as a parameter to UserUtilities.

Related

Integer Picker to select in swiftui xcode

I am making a project with qr code generator and i don't know how to use integer with picker and i want to ask what code did i missed in the function and the view. does any expert know how to solve it, thank you for the help.
my code:
#State private var sSecond = Int()
#State var navigated = false
let Second = ["10", "20", "30", "40", "50", "60"]
var body: some View {
Form{
Section {
VStack{
Picker(selection: $sSecond, label: Text("Select Seconds"))
{
ForEach(0 ..< Second.count) {
index in Text(self.Second[index]).tag(index)
}
}
}
}
NavigationLink(destination: Generate(Second: $sSecond), isActive: self.$navigated)
{
Text("Complete")
}
}
Function:
import Foundation
import SwiftUI
import CoreImage.CIFilterBuiltins
struct Generate: View {
#State var second = Int()
let filter = CIFilter.qrCodeGenerator()
let cont = CIContext()
var body: some View {
Image(uiImage: imageGenerate(second))
.interpolation(.none)
.resizable()
.frame(width: 150, height: 150, alignment: .center)
}
func imageGenerate(second: Int)-> UIImage {
let data = Data(second)
filter.setValue(data, forKey: "inputMessage")
if let qr = filter.outputImage {
if let qrImage = cont.createCGImage(qr, from: qr.extent){
return UIImage(cgImage: qrImage)
}
}
return UIImage(systemName: "xmark") ?? UIImage()
}
}
How Can I Show 10 in qr code, thats what i expected in this questions
As for your first question: Picker on Int goes like this:
struct PickerInt: View {
let secondsArray = [10, 20, 30, 40, 50, 60] // Int instead of String
#State private var sSecond: Int = 10
var body: some View {
Form{
Picker(selection: $sSecond, label: Text("Select Seconds"))
{
ForEach(secondsArray, id: \.self) { sec in
Text("\(sec)").tag(sec)
}
}
}
}
}

Show a sheet in response to a drop

I'm implementing drag and drop, and have a case where I need the user to decide what to do in response to a drop. So I want to bring up a sheet to ask the user for input. The problem is that the sheet doesn't appear until I drag another item to the same view. This does make sense, so I'm looking for a way to handle this differently.
The current approach looks like this (simplified):
struct SymbolInfo {
enum SymbolType {
case string, systemName
}
var type: SymbolType
var string: String
}
struct MyView: View, DropDelegate {
#State var sheetPresented = false
#State var droppedText = ""
static let dropTypes = [UTType.utf8PlainText]
var textColor = NSColor.white
private var frameRect: CGRect = .null
private var contentPath: Path = Path()
private var textRect: CGRect = .null
#State private var displayOutput: SymbolInfo
#State private var editPopoverIsPresented = false
// There's an init to set up the display output, the various rects and path
var body: some View {
ZStack(alignment: stackAlignment) {
BackgroundView() // Draws an appropriate background
.frame(width: frameRect.width, height: frameRect.height)
if displayOutput.type == .string {
Text(displayOutput.string)
.frame(width: textRect.width, height: textRect.height, alignment: .center)
.foregroundColor(textColour)
.font(displayFont)
.allowsTightening(true)
.lineLimit(2)
.minimumScaleFactor(0.5)
}
else {
Image(systemName: displayOutput.string)
.frame(width: textRect.width, height: textRect.height, alignment: .center)
.foregroundColor(textColour)
.minimumScaleFactor(0.5)
}
}
.onAppear {
// Retrieve state information from the environment
}
.focusable(false)
.allowsHitTesting(true)
.contentShape(contentPath)
.onHover { entered in
// Populates an inspector
}
.onTapGesture(count: 2) {
// Handle a double click
}
.onTapGesture(count: 1) {
// Handle a single click
}
.popover(isPresented: $editPopoverIsPresented) {
// Handles a popover for editing data
}
.onDrop(of: dropTypes, delegate: self)
.sheet(sheetPresented: $sheetPresented, onDismiss: sheetReturn) {
// sheet to ask for the user's input
}
}
func sheetReturn() {
// act on the user's input
}
func performDrop(info: DropInfo) -> Bool {
if let item = info.itemProviders(for: dropTypes).first {
item.loadItem(forTypeIdentifier: UTType.utf8PlainText.identifier, options: nil) { (textData, error) in
if let textData = String(data: textData as! Data, encoding: .utf8) {
if (my condition) {
sheetIsPresented = true
droppedText = textData
}
else {
// handle regular drop
}
}
}
return true
}
return false
}
}
So my reasoning is that the drop sets sheetPresented to true, but then it doesn't get acted on until the view is rebuilt, such as on dragging something else to it. But I'm still new to SwiftUI, so I may be incorrect.
Is there a way to handle this kind of interaction that I haven't found?
I never was able to exactly reproduce the problem, but the issue related to trying to have more than one kind of sheet that could be shown, depending on conditions. The solution was to break up the original view into a family of views that encapsulated the different behaviours, and show the appropriate one rather than try to make one view do everything.
I won't show the whole code, since it's too deeply embedded in the app, but here's a demo app that works correctly:
import SwiftUI
import UniformTypeIdentifiers
#main
struct DragAndDropSheetApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
HStack() {
TargetView(viewType: .normal, viewText: "A")
.frame(width: 40, height: 40, alignment: .top)
TargetView(viewType: .protected, viewText: "B")
.frame(width: 40, height: 40, alignment: .top)
TargetView(viewType: .normal, viewText: "C")
.frame(width: 40, height: 40, alignment: .top)
TargetView(viewType: .protected, viewText: "D")
.frame(width: 40, height: 40, alignment: .top)
}
.padding()
}
}
enum ViewType {
case normal, protected
}
struct TargetView: View, DropDelegate {
#State private var sheetPresented = false
#State var viewType: ViewType
#State var viewText: String
#State private var dropText = ""
#State private var dropType: DropActions = .none
static let dropTypes = [UTType.utf8PlainText]
var body: some View {
ZStack(alignment: .center) {
Rectangle()
.foregroundColor(viewType == .normal ? .blue : .red)
Text(viewText)
.foregroundColor(.white)
.frame(width: nil, height: nil, alignment: .center)
}
.focusable(false)
.allowsHitTesting(true)
.onDrop(of: TargetView.dropTypes, delegate: self)
.sheet(isPresented: $sheetPresented, onDismiss: handleSheetReturn) {
ProtectedDrop(isPresented: $sheetPresented, action: $dropType)
}
}
func handleSheetReturn() {
switch dropType {
case .append:
viewText += dropText
case .replace:
viewText = dropText
case .none:
// Nothing to do
return
}
}
func performDrop(info: DropInfo) -> Bool {
if let item = info.itemProviders(for: TargetView.dropTypes).first {
item.loadItem(forTypeIdentifier: UTType.utf8PlainText.identifier, options: nil) { textData, error in
if let textData = String(data: textData as! Data, encoding: .utf8) {
if viewType == .normal {
viewText = textData
}
else {
dropText = textData
sheetPresented = true
}
}
}
return true
}
return false
}
}
enum DropActions: Hashable {
case append, replace, none
}
struct ProtectedDrop: View {
#Binding var isPresented: Bool
#Binding var action: DropActions
var body: some View {
VStack() {
Text("This view is protected. What do you want to do?")
Picker("", selection: $action) {
Text("Append the dropped text")
.tag(DropActions.append)
Text("Replace the text")
.tag(DropActions.replace)
}
.pickerStyle(.radioGroup)
HStack() {
Spacer()
Button("Cancel") {
action = .none
isPresented.toggle()
}
.keyboardShortcut(.cancelAction)
Button("OK") {
isPresented.toggle()
}
.keyboardShortcut(.defaultAction)
}
}
.padding()
}
}

SwiftUI - Using a Picker on another view to sort results from API call

I have a view that makes an API call to pull nearby restaurant data and display it in a list. I've placed a button in the navigation bar of that view to display a sheet which will ultimately show the user filter options. For now, it uses a Picker placed in a form to allow the user to pick how they want to sort the results. I am using a variable with a #Binding property wrapper in the filter view to pass the selected value to a #State variable in the restaurant data view (selectedOption). It all works great until the view is reloaded. Either by going to a different view or relaunching the app. It appears the selectedOption variable in my API call in the onAppear function of the restaurant data view is being reset to what I've set the default value to when I defined the #State variable. I am wondering if there is a way to persist the value of what was chosen in the filter view through view reloads of the restaurant data view.
Restaurant data view:
import SwiftUI
import SwiftyJSON
import SDWebImageSwiftUI
struct RestaurantsView: View {
#EnvironmentObject var locationViewModel: LocationViewModel
#EnvironmentObject var venueDataViewModel: VenueDataViewModel
#State var selectedOption: String = "rating"
#State private var showingSheet = false
#State private var searchText = ""
#State private var showCancelButton: Bool = false
var body: some View {
let CPLatitude: Double = locationViewModel.lastSeenLocation?.coordinate.latitude ?? 0.00
let CPLongitude: Double = locationViewModel.lastSeenLocation?.coordinate.longitude ?? 0.00
GeometryReader { geometry in
VStack {
Text("Restaurants")
.padding()
List(venueDataViewModel.venuesListTen) { index in
NavigationLink(destination: RestaurantsDetailView(venue: index)) {
HStack {
VStack(alignment: .leading, spacing: 6) {
Text(index.name ?? "")
.font(.body)
.lineLimit(2)
Text(index.address ?? "")
.font(.subheadline)
.lineLimit(2)
}
}
}
Spacer()
if index.image != nil {
WebImage(url: URL(string: index.image ?? ""), options: .highPriority, context: nil)
.resizable()
.frame(width: 70, height: 70, alignment: .center)
.aspectRatio(contentMode: .fill)
.cornerRadius(12)
}
}
Text("Selected: \(selectedOption)")
Spacer()
}.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
showingSheet.toggle()
}, label: {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
)
.sheet(isPresented: $showingSheet, content: {
NavigationView {
RestaurantsFilterView(selectedOption: self.$selectedOption)
}
})
}
}
.onAppear {
venueDataViewModel.retrieveVenues(latitude: CPLatitude, longitude: CPLongitude, category: "restaurants", limit: 50, sortBy: selectedOption, locale: "en_US") { (response, error) in
if let error = error {
print("\(error)")
}
}
}
}
}
}
Filter view:
import SwiftUI
struct RestaurantsFilterView: View {
#EnvironmentObject var locationViewModel: LocationViewModel
#EnvironmentObject var venueDataViewModel: VenueDataViewModel
var sortOptions = ["rating", "review_count"]
#Binding var selectedOption: String
var body: some View {
let CPLatitude: Double = locationViewModel.lastSeenLocation?.coordinate.latitude ?? 0.00
let CPLongitude: Double = locationViewModel.lastSeenLocation?.coordinate.longitude ?? 0.00
VStack {
Text("Filter")
Form {
Section {
Picker(selection: $selectedOption, label: Text("Sort")) {
ForEach(sortOptions, id: \.self) {
Text($0)
}
}.onChange(of: selectedOption) { Equatable in
venueDataViewModel.retrieveVenues(latitude: CPLatitude, longitude: CPLongitude, category: "restaurants", limit: 50, sortBy: selectedOption, locale: "en_US") { (response, error) in
if let error = error {
print("\(error)")
}
}
}
}
}
}
}
}
I am still new to Swift and SwiftUI so I appreciate the help.
Thank you!

How to display an image where the user taps? SwiftUI

I am making a mining tapping game and I want to display a hammer wherever the user taps.
I mean, wherever the user taps the hammer image will stay on for one second.
Is there a way to do it?
My example code is below:
struct Level1: View {
#State var tapScore = 0
#State var showingMinedHammer = false
func showMinedHammer() {
self.showingMinedHammer = true
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.showingMinedHammer = false
}
}
func mine() {
tapScore += 1
showMinedHammer()
}
var body: some View {
GeometryReader { geometryProxy in
ZStack {
Image("mine1").resizable().frame(width: UIScreen.main.bounds.height * 1.4, height: UIScreen.main.bounds.height)
.onTapGesture {
self.mine()
}
if self.showingMinedHammer {
Image(systemName: "hammer.fill")
.resizable()
.frame(width: 30, height: 30)
}
}
}.edgesIgnoringSafeArea(.all)
}
}
It just need to read location of tap and use it as position for hammer image, like below - all by SwiftUI
Tested with Xcode 11.4 / iOS 13.4
Here is modified only part
#State private var location = CGPoint.zero // < here !!
var body: some View {
GeometryReader { geometryProxy in
ZStack {
Image("mine1").resizable().frame(width: UIScreen.main.bounds.height * 1.4, height: UIScreen.main.bounds.height)
.gesture(DragGesture(minimumDistance: 0).onEnded { value in
self.location = value.location // < here !!
self.mine()
})
if self.showingMinedHammer {
Image(systemName: "hammer.fill")
.resizable()
.frame(width: 30, height: 30)
.position(self.location) // < here !!
}
}
}.edgesIgnoringSafeArea(.all)
}
To get the location of where you tapped, you can do something like this:
import SwiftUI
struct ContentView: View {
#State var points:[CGPoint] = [CGPoint(x:0,y:0), CGPoint(x:50,y:50)]
var body: some View {
ZStack{
GetTapLocation {
// tappedCallback
location in
self.points.append(location)
print(self.points)
}
}
}
}
struct GetTapLocation:UIViewRepresentable {
var tappedCallback: ((CGPoint) -> Void)
func makeUIView(context: UIViewRepresentableContext<GetTapLocation>) -> UIView {
let v = UIView(frame: .zero)
let gesture = UITapGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.tapped))
v.addGestureRecognizer(gesture)
return v
}
class Coordinator: NSObject {
var tappedCallback: ((CGPoint) -> Void)
init(tappedCallback: #escaping ((CGPoint) -> Void)) {
self.tappedCallback = tappedCallback
}
#objc func tapped(gesture:UITapGestureRecognizer) {
let point = gesture.location(in: gesture.view)
self.tappedCallback(point)
}
}
func makeCoordinator() -> GetTapLocation.Coordinator {
return Coordinator(tappedCallback:self.tappedCallback)
}
func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<GetTapLocation>) {
}
}
There has to be a simpler implementation, but until then you can get the location where you tapped. I hope that helps :)

Scroll SwiftUI List to new selection

If you have a SwiftUI List with that allows single selection, you can change the selection by clicking the list (presumably this makes it the key responder) and then using the arrow keys. If that selection reaches the end of the visible area, it will scroll the whole list to keep the selection visible.
However, if the selection object is updated in some other way (e.g. using a button), the list will not be scrolled.
Is there any way to force the list to scroll to the new selection when set programmatically?
Example app:
import SwiftUI
struct ContentView: View {
#State var selection: Int? = 0
func changeSelection(_ by: Int) {
switch self.selection {
case .none:
self.selection = 0
case .some(let sel):
self.selection = max(min(sel + by, 20), 0)
}
}
var body: some View {
HStack {
List((0...20), selection: $selection) {
Text(String($0))
}
VStack {
Button(action: { self.changeSelection(-1) }) {
Text("Move Up")
}
Button(action: { self.changeSelection(1) }) {
Text("Move Down")
}
}
}
}
}
I tried several solutions, one of them I'm using in my project (I need horizontal paging for 3 lists). And here are my observations:
I didn't find any methods to scroll List in SwiftUI, there is no mention about it in documentation yet;
You may try ScrollView (my variant below, here is other solution), but these things might look monstroid;
Maybe the best way is to use UITableView: tutorial from Apple and try scrollToRowAtIndexPath method (like in this answer).
As I wrote, here is my example, which, of course, requires refinement. First of all ScrollView needs to be inside GeometryReader and you can understand the real size of content. The second thing is that you need to control your gestures, which might be difficult. And the last one: you need to calculate current offset of ScrollViews's content and it could be other than in my code (remember, I tried to give you example):
struct ScrollListView: View {
#State var selection: Int?
#State private var offset: CGFloat = 0
#State private var isGestureActive: Bool = false
func changeSelection(_ by: Int) {
switch self.selection {
case .none:
self.selection = 0
case .some(let sel):
self.selection = max(min(sel + by, 30), 0)
}
}
var body: some View {
HStack {
GeometryReader { geometry in
VStack {
ScrollView(.vertical, showsIndicators: false) {
ForEach(0...29, id: \.self) { line in
ListRow(line: line, selection: self.$selection)
.frame(height: 20)
}
}
.content.offset(y: self.isGestureActive ? self.offset : geometry.size.height / 4 - CGFloat((self.selection ?? 0) * 20))
.gesture(DragGesture()
.onChanged({ value in
self.isGestureActive = true
self.offset = value.translation.width + -geometry.size.width * CGFloat(self.selection ?? 1)
})
.onEnded({ value in
DispatchQueue.main.async { self.isGestureActive = false }
}))
}
}
VStack {
Button(action: { self.changeSelection(-1) }) {
Text("Move Up")
}
Spacer()
Button(action: { self.changeSelection(1) }) {
Text("Move Down")
}
}
}
}
}
of course you need to create your own "list row":
struct ListRow: View {
#State var line: Int
#Binding var selection: Int?
var body: some View {
HStack(alignment: .center, spacing: 2){
Image(systemName: line == self.selection ? "checkmark.square" : "square")
.padding(.horizontal, 3)
Text(String(line))
Spacer()
}
.onTapGesture {
self.selection = self.selection == self.line ? nil : self.line
}
}
}
hope it'll be helpful.
In the new relase of SwiftUI for iOs 14 and MacOs Big Sur they added the ability to programmatically scroll to a specific cell using the new ScrollViewReader:
struct ContentView: View {
let colors: [Color] = [.red, .green, .blue]
var body: some View {
ScrollView {
ScrollViewReader { value in
Button("Jump to #8") {
value.scrollTo(8)
}
ForEach(0..<10) { i in
Text("Example \(i)")
.frame(width: 300, height: 300)
.background(colors[i % colors.count])
.id(i)
}
}
}
}
}
Then you can use the method .scrollTo() like this
value.scrollTo(8, anchor: .top)
Credit: www.hackingwithswift.com
I am doing it this way:
1) Reusable copy-paste component:
import SwiftUI
struct TableViewConfigurator: UIViewControllerRepresentable {
var configure: (UITableView) -> Void = { _ in }
func makeUIViewController(context: UIViewControllerRepresentableContext<TableViewConfigurator>) -> UIViewController {
UIViewController()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<TableViewConfigurator>) {
//let tableViews = uiViewController.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()
let tableViews = UIApplication.nonModalTopViewController()?.navigationController?.topViewController?.view.subviews(ofType: UITableView.self) ?? [UITableView]()
for tableView in tableViews {
self.configure(tableView)
}
}
}
2) Extension on UIApplication to find top view controller in hierarchy
extension UIApplication {
class var activeSceneRootViewController: UIViewController? {
if #available(iOS 13.0, *) {
for scene in UIApplication.shared.connectedScenes {
if scene.activationState == .foregroundActive {
return ((scene as? UIWindowScene)?.delegate as? UIWindowSceneDelegate)?.window??.rootViewController
}
}
} else {
// Fallback on earlier versions
return UIApplication.shared.keyWindow?.rootViewController
}
return nil
}
class func nonModalTopViewController(controller: UIViewController? = UIApplication.activeSceneRootViewController) -> UIViewController? {
print(controller ?? "nil")
if let navigationController = controller as? UINavigationController {
return nonModalTopViewController(controller: navigationController.topViewController ?? navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return nonModalTopViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
let top = nonModalTopViewController(controller: presented)
if top == presented { // just modal
return controller
} else {
print("Top:", top ?? "nil")
return top
}
}
if let navigationController = controller?.children.first as? UINavigationController {
return nonModalTopViewController(controller: navigationController.topViewController ?? navigationController.visibleViewController)
}
return controller
}
}
3) Custom part - Here you implement your solution for UITableView behind List like scrolling:
Use it like modifier on any view in List in View
.background(TableViewConfigurator(configure: { tableView in
if self.viewModel.statusChangeMessage != nil {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
let lastIndexPath = IndexPath(row: tableView.numberOfRows(inSection: 0) - 1, section: 0)
tableView.scrollToRow(at: lastIndexPath, at: .bottom, animated: true)
}
}
}))

Resources