Navigate SwiftUI page from WKWebView operation - ios

I am a newbie in IOS Programming and also SwiftUI. Coming from Java/Kotlin Android. I want to learn SwiftUI.
I have a WKWebView. I want to change SwiftUI page according to url changes in WKWebView. I have done a lot of work as you can see below. But I am struggled in navigation.
struct ContentView: View, Listener {
func onFetched() {
NavigationLink(destination: MainView()) {/* HERE this is not working.
App never goes to MainView.swift page.*/
Text("Show Detail View")
}
}
var body: some View {
NavigationView {
VStack {
WebView(authListener: self)
}.navigationBarTitle(Text("PEAKUP Velocity"))
}
}
}
struct WebView: UIViewRepresentable {
var listener: Listener
#ObservedObject var observe = observable()
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
observe.observation = uiView.observe(\WKWebView.url, options: .new) { view, change in
if let url = view.url {
self.observe.loggedIn = true // We loaded the page
self.listener.onFetched()
uiView.isHidden = true
}
}
uiView.load("https://google.com")
}
}
protocol Listener {
func onFetched()
}
Update: I tried this in onFetched():
NavigationLink(destination: MainView()) { Text("") }''''
And I tried this piece of code:
NavigationView {
NavigationLink(destination: SecondView()){
Text("Navigation Link")
}
}
Update 2: I tried this code also in onFetched:
self.presentation(Model(MainView(), onDismiss: nil))
Update 3: I tried this:
self.sheet(isPresented: $sayHello) {
MainView()
}

** Disclaimer I haven't used WKWebviews in the context of SwiftUI, but I assume same would be true. **
I would recommend looking into WKNavigationDelegate. Unless your class is of type WKWebView you need to ensure to assign a delegate to handle any navigation events.
Perhaps you'll find the below article helpful: https://www.hackingwithswift.com/articles/112/the-ultimate-guide-to-wkwebview

Related

How to use SWCollaborationView in SwiftUI?

SWCollaborationView was introduced as a standard UI element to manage real-time collaboration between users in iOS16+. This is explained in this Apple article. Similarly to UICloudSharingController, it's way to view participants to a share and manage sharing.
Given that UICloudSharingController is broken in iOS16+ (see this), how can I use SWCollaborationView in SwiftUI?
My failed attempts so far:
The the WWDC22 talk introducing SWCollaborationView, the speaker embedded SWCollaborationView in UIBarButtonItem(customView:). I was not able to embed UIBarButtonItem(customView:) into my SwiftUI lifecycle app, because UIBarButtonItem does not conform to UIView and therefore cannot be introduced using UIViewRepresentable.
I also tried wrapping SWCollaborationView in UIViewRepresentable and introducing it directly. The result was identical to this post. When I wrapped it in ToolbarItem, the icon appeared but no action happened on tap. When I wrapped it in ToolbarItem and Button that would open an identical collaboration view as a popover, the icon appeared (screenshot1) and on tap opened a popover where the same icon would appear (screenshot2. Only a second tap would correctly open the desired popover inherent to SWCollaborationView (screenshot3). The code for this is below.
import SwiftUI
import CloudKit
import SharedWithYou
struct DetailView: View {
var photo: Photo // some object saved in CloudKit, in this example a photo
#State var showPopover = false
#Binding var activeCover: ActiveCover? // for dismissal (not relevant for SWCollaborationView)
var body: some View {
NavigationView {
VStack {
Text("Some content of detail view")
}
.toolbar {
if let existingShare = PersistenceController.shared.existingShare(photo: photo) { // get the existing CKShare for this object (in this case we use NSPersistentCloudKitContainer.fetchShares, but that's not really relevant)
ToolbarItem(placement: .automatic) {
Button(action: {
showPopover.toggle()
}){
CollaborationView(existingShare: existingShare) // <- icon appears, but without the number of participants
}
.popover(isPresented: $showPopover) {
CollaborationView(existingShare: existingShare) // <- icon appears AGAIN, with the number of participants. Instead, we want a popover inherent to SWCollabroationView!
}
}
}
ToolbarItem(placement: .automatic) {
Button("Dismiss") { activeCover = nil }
}
}
}
}
}
struct CollaborationView: UIViewRepresentable {
let existingShare: CKShare
func makeUIView(context: Context) -> SWCollaborationView {
let itemProvider = NSItemProvider()
itemProvider.registerCKShare(existingShare,
container: PersistenceController.shared.cloudKitContainer,
allowedSharingOptions: .standard)
let collaborationView = SWCollaborationView(itemProvider: itemProvider)
collaborationView.activeParticipantCount = existingShare.participants.count
return collaborationView
}
func updateUIView(_ uiView: SWCollaborationView, context: Context) {
}
}
In SwiftUi it's called ShareLink
I submitted a TSI about this; Apple confirmed that SWCollaborationView is not compatible with SwiftUI at the moment and we should submit feedback.

How to update View from callbacks from a given viewController in swift ui

I have a state String variable called fullLogMessages
In my ViewController I set the fullLogMessage through given callbacks. This fulllogMessage I then get from a LogViewModel I created
I can get it to update a TextField on a button click to add a request has been sent, but unsure how to update the Textfield for the subsequent callbacks.
SwiftUI
import SwiftUI
struct TestAdsView: View {
#StateObject var lvm: LogsViewModel = LogsViewModel()
var body: some View {
VStack{
Text(vm.adStatus.rawValue)
TextField("Logs", text: $lvm.getFullLogMessage)
Button("load Ads", action: {
vm.loadAds()
})
AdsScreenView_UI(viewModel: vm).frame(width: 0, height: 0)
}
}
}
struct AdsScreenView_UI: UIViewControllerRepresentable{
#ObservedObject var viewModel: AdsScreenViewModel
func makeUIViewController(context: Context) -> some AdsScreenViewController {
print(#function)
return AdsScreenViewController(viewModel: viewModel)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
print(#function)
}
}
I'm a bit stuck on how I can get the TextField to update from getting the message in the updateUIViewController. This method gets called after each callback but no idea how to update the view with the given message.

Navigation bar disappears after the app comes to the foreground again

I'm using Parchment to add menu items at the top. The hierarchy of the main view is the following:
NavigationView
-> TabView
--> Parchment PagingView
---> NavigationLink(ChildView)
All works well going to the child view and then back again repeatedly. The issue happens when I go to ChildView, then go to the background/Home Screen then re-open. If I click back and then go to the child again the back button and the whole navigation bar disappears.
Here's code to replicate:
import SwiftUI
import Parchment
#main
struct ParchmentBugApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
PagingView(items: [
PagingIndexItem(index: 0, title: "View 0"),
]) { item in
VStack {
NavigationLink(destination: ChildView()) {
Text("Go to child view")
}
}
.navigationBarHidden(true)
}
}
}
}
}
struct ChildView: View {
var body: some View {
VStack {
Text("Child View")
}
.navigationBarHidden(false)
.navigationBarTitle("Child View")
}
}
To replicate:
Launch and go to the child view
Click the home button to send the app to the background
Open the app again
Click on back
Navigate to the child view. The nav bar/back button are not there anymore.
What I noticed:
Removing the TabView makes the problem go away.
Removing PagingView also makes the problem go.
I tried to use a custom PagingController and played with various settings without success. Here's the custom PagingView if someone would like to tinker with the settings as well:
struct CustomPagingView<Item: PagingItem, Page: View>: View {
private let items: [Item]
private let options: PagingOptions
private let content: (Item) -> Page
/// Initialize a new `PageView`.
///
/// - Parameters:
/// - options: The configuration parameters we want to customize.
/// - items: The array of `PagingItem`s to display in the menu.
/// - content: A callback that returns the `View` for each item.
public init(options: PagingOptions = PagingOptions(),
items: [Item],
content: #escaping (Item) -> Page) {
self.options = options
self.items = items
self.content = content
}
public var body: some View {
PagingController(items: items, options: options,
content: content)
}
struct PagingController: UIViewControllerRepresentable {
let items: [Item]
let options: PagingOptions
let content: (Item) -> Page
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<PagingController>) -> PagingViewController {
let pagingViewController = PagingViewController(options: options)
return pagingViewController
}
func updateUIViewController(_ pagingViewController: PagingViewController, context: UIViewControllerRepresentableContext<PagingController>) {
context.coordinator.parent = self
if pagingViewController.dataSource == nil {
pagingViewController.dataSource = context.coordinator
} else {
pagingViewController.reloadData()
}
}
}
class Coordinator: PagingViewControllerDataSource {
var parent: PagingController
init(_ pagingController: PagingController) {
self.parent = pagingController
}
func numberOfViewControllers(in pagingViewController: PagingViewController) -> Int {
return parent.items.count
}
func pagingViewController(_: PagingViewController, viewControllerAt index: Int) -> UIViewController {
let view = parent.content(parent.items[index])
return UIHostingController(rootView: view)
}
func pagingViewController(_: PagingViewController, pagingItemAt index: Int) -> PagingItem {
return parent.items[index]
}
}
}
Tested on iOS Simulator 14.4 & 14.5, and device 14.5 beta 2.
Any tips or ideas are very much appreciated.
Okay I found the issue while debugging something else that was related to Parchment as well.
The issue is updateUIViewController() gets called each time the encompassing SwiftUI state changes (and when coming back to the foreground), and the PageController wrapper provided by the library will call reloadData() since the data source data has already been set. So to resolve this just remove/comment out the reloadData() call since the PageController will be re-built if the relevant state changes. The same issue was the cause for the bug I was debugging.
func updateUIViewController(_ pagingViewController: PagingViewController, context: UIViewControllerRepresentableContext<PagingController>) {
context.coordinator.parent = self
if pagingViewController.dataSource == nil {
pagingViewController.dataSource = context.coordinator
}
//else {
// pagingViewController.reloadData()
//}
}

Is there a SwiftUI equivalent to WKInterfaceController's .becomeCurrentPage method?

Previously in WatchKit we could tell a certain InterfaceController to present itself using .becomeCurrentPage, how can we do it in Swift UI?
In WatchKit for example I would:
// handle notification
#objc func respondToWaterlock(_ notification: NSNotification) {
self.becomeCurrentPage()
}
there is no equivalent for becomeCurrentPage method in SwiftUI. You can update your State or ViewModel to achieve a similar result.
for example:
enum Pages {
case home
case settings
}
struct MyView: View {
#State var selectedPage: Pages = .home
var body: some View {
Group {
if self.selectedPage == .home {
Text("Home")
} else if self.selectedPage == .settings {
Text("Settings")
}
}
}
}
You should just update the selectedPage state variable to change the page.

How to create an extension on a view to show alerts on long press gesture - SwiftUI

I am creating an app in SwiftUI on iOS 13 in Xcode 11.6
I want to create an extension on SwiftUI's View that shows an alert message when a user long presses on the view.
For example, suppose I have a view like so:
import SwiftUI
struct TestView: View {
var body: some View {
TabView {
Text("1").addLongPressAlert("Test 1")
Text("2").addLongPressAlert("Test 2")
Text("3").addLongPressAlert("Test 3")
}
}
}
The extension on View would look something like this:
extension View {
public func addLongPressAlert(message _ : String) -> some View {
return self.onLongPressGesture {
// I know this is not how you show an alert, but im unsure how to display it
Alert(title: Text("Alert"), message: Text(m), dismissButton: .default(Text("OK!")))
}
}
}
I am struggling to figure out how to set this up correctly.
Does anyone know how to achieve this?
You can create a custom ViewModifier:
struct LongPressAlertModifier: ViewModifier {
#State var showAlert = false
let message: String
func body(content: Content) -> some View {
content
.onLongPressGesture {
self.showAlert = true
}
.alert(isPresented: $showAlert) {
Alert(title: Text("Alert"), message: Text(message), dismissButton: .default(Text("OK!")))
}
}
}
and use it like this:
Text("1").modifier(LongPressAlertModifier(message: "Test1"))
You can even create a custom View extension:
extension View {
func addLongPressAlert(_ message: String) -> some View {
self.modifier(LongPressAlertModifier(message: message))
}
}
and use your modifier in a more convenient way:
Text("1").addLongPressAlert("Test 1")

Resources