According to wwdc 17 there is a way to observe cookies changes in WKWebView in iOs 11 (WebsiteDataStore.httpCookieStore).
Could you provide example how to do that?
I found that there is WKHTTPCookieStoreObserver and it has cookiesDidChange member.
So i put that protocol as following
class ActivitiesViewController: UIViewController, UIGestureRecognizerDelegate, WKNavigationDelegate, WKHTTPCookieStoreObserver {
and
func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
but cookiesDidChange not fires (
You must add the view controller as an observer of the web data store to trigger the cookiesDidChange(in:) method.
// These two lines occur in the viewDidLoad method of a UIViewController class
// This view controller conforms to the WKHTTPCookieStoreObserver protocol
WKWebsiteDataStore.default().httpCookieStore.add(self)
let webView = WKWebView()
// Configure and load the web view
Note: In iOS 11.3 a change occurred that requires the WKWebsiteDataStore.default().httpCookieStore.add(self) line to occur before the WKWebView object is created.
Related
I'm sending push notification's with OneSignal, I'm receiving them fine. My problem is, is that when I click on the notification, I'd like it to open the notification url. I have the notificationUrl, but loading the url in my WKWebView is my problem. I am using SwiftUi App lifecycle, and not UiKit. So take in note that I don't have a ViewController.
I've tried using UIApplication.shared.open(URL(string: notificationUrl)!) but this opens the link in the browser.
Here's my notification opened handler in AppDelegate.
.....
let osNotificationOpenedBlock: OSNotificationOpenedBlock = { resultObj in
let notification: OSNotification = resultObj.notification
let actionType = resultObj.action.type
let dataObj = resultObj.notification.additionalData
let notificationUrl = "\(dataObj?["notificationUrl"] ?? "")"
UIApplication.shared.open(URL(string: notificationUrl)!)
}
OneSignal.setNotificationOpenedHandler(osNotificationOpenedBlock)
....
What worked for me, is to make the webview variable a global variable in the code. So in WebView.swift or your webview file, put the WKWebView variable on the outside of the struct and apply:
var wbWebViewEl: WKWebView = WKWebView()
struct SwiftUiWebView: UIViewRepresentable {
...
Then, instead of:
UIApplication.shared.open(URL(string: notificationUrl)!)
Do:
var notificationRequestEl = URLRequest(url: url!)
wbWebViewEl.load(notificationRequestEl)
I have a simple widget (medium-sized) with two texts, and what I want is to be able to perform a deep link to lead the user to a specific section of my app, but I can't seem to find a way to do so.
The view I have written (which is very simple):
HStack {
Text("FIRST ITEM")
Spacer()
Text("SECOND ITEM")
}
I have already tried to replace
Text("SECOND ITEM")
with
Link("SECOND ITEM destination: URL(string: myDeeplinkUrl)!)
but it doesn't work either.
In the Widget view you need to create a Link and set its destination url:
struct SimpleWidgetEntryView: View {
var entry: SimpleProvider.Entry
var body: some View {
Link(destination: URL(string: "widget://link1")!) {
Text("Link 1")
}
}
}
Note that Link works in medium and large Widgets only. If you use a small Widget you need to use:
.widgetURL(URL(string: "widget://link0")!)
In your App view receive the url using onOpenURL:
#main
struct WidgetTestApp: App {
var body: some Scene {
WindowGroup {
Text("Test")
.onOpenURL { url in
print("Received deep link: \(url)")
}
}
}
}
It is also possible to receive deep links in the SceneDelegate by overriding:
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>)
You can find more explanation on how to use this function in this thread:
Detect app launch from WidgetKit widget extension
Here is a GitHub repository with different Widget examples including the DeepLink Widget.
Also, you can do it using AppDelegate (if you not using SceneDelegate):
.widgetURL(URL(string: "urlsceheme://foobarmessage"))
// OR
Link(destination: URL(string: "urlsceheme://foobarmessage")!) {
Text("Foo")
}
Set this code within AppDelegate
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
let message = url.host?.removingPercentEncoding // foobarmessage
return true
}
See docs on: Respond to User Interactions
When users interact with your widget, the system launches your app to handle the request. When the system activates your app, navigate to the details that correspond to the widget’s content. Your widget can specify a URL to inform the app what content to display. To configure custom URLs in your widget:
For all widgets, add the widgetURL(_:) view modifier to a view in your widget’s view hierarchy. If the widget’s view hierarchy includes more than one widgetURL modifier, the behavior is undefined.
For widgets that use WidgetFamily.systemMedium or WidgetFamily.systemLarge, add one or more Link controls to your widget’s view hierarchy. You can use both widgetURL and Link controls. If the interaction targets a Link control, the system uses the URL in that control. For interactions anywhere else in the widget, the system uses the URL specified in the widgetURL view modifier.
For example, a widget that displays details of a single character in a game can use widgetURL to open the app to that character’s detail.
#ViewBuilder
var body: some View {
ZStack {
AvatarView(entry.character)
.widgetURL(entry.character.url)
.foregroundColor(.white)
}
.background(Color.gameBackground)
}
If the widget displays a list of characters, each item in the list can be in a Link control. Each Link control specifies the URL for the specific character it displays.
When the widget receives an interaction, the system activates the containing app and passes the URL to onOpenURL(perform:), application(_:open:options:), or application(_:open:), depending on the life cycle your app uses.
If the widget doesn’t use widgetURL or Link controls, the system activates the containing app and passes an NSUserActivity to onContinueUserActivity(_:perform:), application(_:continue:restorationHandler:), or application(_:continue:restorationHandler:). The user activity’s userInfo dictionary contains details about the widget the user interacted with. Use the keys in WidgetCenter.UserInfoKey to access these values from Swift code. To access the userInfo values from Objective-C, use the keys WGWidgetUserInfoKeyKind and WGWidgetUserInfoKeyFamily instead.
first question on StackOverflow (and relatively new to native app dev targeting macOS).
I'm currently trying to build a simple SwiftUI view, that leverages WKWebView's loadHTMLString function, to display hardcoded HTML string on the screen.
AFAIK webkit does not support SwiftUI as of the moment, so I need to embed AppKit UI in my SwiftUI app using NSViewRepresentable. This what I got so far following the docs and XCode's autocomplete:
import SwiftUI
import WebKit
struct HTMLView: NSViewRepresentable {
typealias NSViewType = WKWebView
let html = "<h1>Hello wordl</h1>"
func makeNSView(context: Context) -> WKWebView {
let webview = WKWebView()
return webview
}
func updateNSView(_ nsView: WKWebView, context: Context) {
nsView.loadHTMLString(html, baseURL: nil)
}
}
struct HTMLView_Previews: PreviewProvider {
static var previews: some View {
HTMLView()
}
}
Should be noted that preview canvas does not load the HTML (displays empty window).
I then replace the default Text() view in ContentView.Swift with HTMLView(), and run my application.
The application compiles, but the WebView fails to load the HTML (I get an empty window). I get the following errors in console:
WebPageProxy::processDidTerminate: (pid 0), reason 3
WebPageProxy::dispatchProcessDidTerminate: reason = 3
WebPageProxy::processDidTerminate: (pid 0), reason 3
WebPageProxy::dispatchProcessDidTerminate: reason = 3
WebPageProxy::tryReloadAfterProcessTermination: process crashed and the client did not handle it, not reloading the page because we reached the maximum number of attempts
Any help with the above would be highly appreciated!
On macOS, although it doesn't seem like this should be necessary, you need to set "Outgoing Connections (Client)" to true in your "Signing and Capabilities" on your target in order for WKWebView to load, even though you're loading from a string and not from an external page.
As soon as I changed this, your example worked fine.
Playing around with examples out there. Found a project that had a class that was a bindableobject and it didn't give any errors. Now that Xcode 11 beta 4 is out, I'm getting the error:
Type 'UserSettings' does not conform to protocol 'BindableObject'
It has a fix button on the error which when you click on that, it adds
typealias PublisherType = <#type#>
It expects you to fill in the type.
What would the type be?
class UserSettings: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
var score: Int = 0 {
didSet {
didChange.send()
}
}
}
Beta 4 Release notes say:
The BindableObject protocol’s requirement is now willChange instead of
didChange, and should now be sent before the object changes rather
than after it changes. This change allows for improved coalescing of
change notifications. (51580731)
You need to change your code to:
class UserSettings: BindableObject {
let willChange = PassthroughSubject<Void, Never>()
var score: Int = 0 {
willSet {
willChange.send()
}
}
}
In Beta 5 they change it again. This time they deprecated BindableObject all together!
BindableObject is replaced by the ObservableObject protocol from the
Combine framework. (50800624)
You can manually conform to ObservableObject by defining an
objectWillChange publisher that emits before the object changes.
However, by default, ObservableObject automatically synthesizes
objectWillChange and emits before any #Published properties change.
#ObjectBinding is replaced by #ObservedObject.
class UserSettings: ObservableObject {
#Published var score: Int = 0
}
struct MyView: View {
#ObservedObject var settings: UserSettings
}
in Xcode 11.X, I verify is fine in Xcode 11.2.1, 11.3.
BindableObject is changed to ObservableObject.
ObjectBinding is now ObservedObject.
didChange should be changed to objectWillChange.
List(dataSource.pictures, id: .self) { }
You can also now get rid of the did/willChange publisher and the .send code and just make pictures #Published
The rest will be autogenerated for you.
for example:
import SwiftUI
import Combine
import Foundation
class RoomStore: ObservableObject {
#Published var rooms: [Room]
init(rooms: [Room]) {
self.rooms = rooms
}
}
struct ContentView: View {
#ObservedObject var store = RoomStore(rooms: [])
}
ref: https://www.reddit.com/r/swift/comments/cu8cqk/getting_the_errors_pictured_below_when_try_to/
SwiftUI and Combine are two new frameworks that were announced at WWDC 2019. These two frameworks received a lot of attention at WWDC 2019, as evidenced by the number of sessions in which these technologies were featured.
SwiftUI was introduced as
a revolutionary, new way to build better apps, faster.
Combine is described as
a unified declarative framework for processing values over time
Between the initial release and now (May, 2020, Swift 5.2), there have been some changes. Anyone new to SwiftUI and Combine, who may have watched the WWDC videos, may be left with a few questions as to how the two frameworks work together.
Combine defines two interfaces: Publisher and Subscriber. A publisher sends events to subscribers. See sequence diagram below.
If you start an application in SwiftUI, and then add combine, there will be no mention of a Publisher or a Subscriber, the two main players required to use Combine. Consider this very simple sample application below.
import SwiftUI
import Combine
import SwiftUI
final class ActorViewModel: ObservableObject {
var name : String
private var imageUrl : URL?
//#Published
private (set) var image : Image = Image(systemName: "photo") {
willSet {
DispatchQueue.main.async {
self.objectWillChange.send()
}
}
}
init(name: String, imageUrl: URL?) {
self.name = name
self.imageUrl = imageUrl
self.fetchImage()
}
private func fetchImage() {
guard nil != self.imageUrl,
String() != self.imageUrl!.absoluteString else { return }
let task = URLSession.shared.dataTask(with: self.imageUrl!) { (data, response, error) in
guard nil == error , nil != response, nil != data,
let uiImage = UIImage(data: data!) else { return }
self.image = Image(uiImage: uiImage)
}
task.resume()
}
}
struct ContentView: View {
#ObservedObject var actor : ActorViewModel
var body: some View {
HStack {
actor.image
.resizable()
.aspectRatio(contentMode: ContentMode.fit)
.frame(width: 60, height: 60)
Text(actor.name)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
let actor = ActorViewModel(name: "Mark Hammill",
imageUrl: URL(string: "https://m.media-amazon.com/images/M/MV5BOGY2MjI5MDQtOThmMC00ZGIwLWFmYjgtYWU4MzcxOGEwMGVkXkEyXkFqcGdeQXVyMzM4MjM0Nzg#._V1_.jpg"))
return ContentView(actor: actor)
}
}
The app preview via the canvas will look like this:
The app uses a list view to display names and images of actors. There are just two classes to consider:
ContentView -- the SwiftUI View subclass
ActorViewModel -- the source of the data for the ContentView (called a ViewModel as it performs the role of VM in MVVM)
The view has a reference to the actor object, as per the class diagram below.
Although this example is using Combine, it is not immediately apparent. There is no mention of a Publisher or a Subscriber. What is going on?
Answer: Looking at the class hierarchy fills in the missing gaps. The below class diagram explains the full picture (click on the image to see it in greater detail).
Consulting Apple's documentation provides definitions for these types:
ObservedObject: A property wrapper type that subscribes to an observable object and invalidates a view whenever the observable object changes.
ObservableObject: A type of object with a publisher that emits before the object has changed. By default an ObservableObject synthesizes an objectWillChange publisher that emits the changed value before any of its #Published properties changes.
objectWillChange: A publisher that emits before the object has changed.
PassthroughSubject: A subject that broadcasts elements to downstream subscribers. As a concrete implementation of Subject, the PassthroughSubject provides a convenient way to adapt existing imperative code to the Combine model.
First, consider what the #ObservedObject means. This is a property wrapper. A property wrapper reduces code duplication, and allows for a succinct syntax when declaring properties that hides how the property is stored and defined. In this case, the "Observed Object" is a property which observes another object.
In other words, the property is a Subscriber (from the Combine Framework). The actor is (through the use of a property wrapper) is a Subscriber, which subscribes to a Publisher, but what is the Publisher in this scenario?
The "Observable Object" is not itself the publisher, but rather has a publisher. The ActorViewModel conforms to the ObservableObject protocol. By doing so, it is provided with a publisher property called objectWillChange by an extension (which the framework provides on the ObservableObject protocol). This objectWillChange property is of type PassthroughSubject, which is a concrete type of the Publisher protocol. The passthrough subject has a property called send, which is a publisher method used to send data to any subscribers. So the property called "objectWillChange" is the Publisher.
To recap, the Subscriber is the property called actor from the ContentView class, and the Publisher is the property objectWillChange from the ActorViewModel class. What about the need for the Subscriber to Subscribe to the Publisher? The "#ObservedObject" property wrapper is itself a Subscriber, so it must subscribe to the Publisher. But how does the View find out about changes sent to the Subscriber? That is handled by the SwiftUI framework, which we never see.
Take-away: we don't need to worry about subscribing the view to the Publisher. On the other hand, we do need to worry about making sure the publisher tell the subscriber when something is about to change. When the image has been fetched from a remote server, and the data has been transformed into an image object, we call objectWillChange.send() to inform the View. Once the subscriber receives notification from the publisher that something is about to / has changed, it invalidates the view (which results in the view redrawing itself).
Summary
The way in which SwiftUI uses a ObservedObject PropertyWrapper does not on the surface give away the fact that Combine even exists in the equation. But by inspecting ObservedObject and ObservableObject, the underlying Combine framework is revealed, along with the design pattern:
subscriber --> subscribing to a publisher --> which then publishes changes --> that are received by the subscriber
References:
Blog Article
WWDC 2019 Session 204
WWDC 2019 Session 226
I am using the following code snippet:
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
self.navigationController.pushViewController(secondViewController, animated: true)
I want to execute this code in the viewDidLoad. I will be using something like this in a series of if statements.
I used some code I found here that utilized a label. It did not work. Can a program move from one viewController without using a button? Almost every example, youtube video uses a button. I will be using a button when it is appropriate
You can not push view controllers before your current view controller loads. You should call your code in your viewDidAppear.
If you plan on directing to a particular view when the app launches I suggest you moving your logic to the AppDelegate in the method: didFinishLaunching. This way you don't need to unnecessarily load views.
Example of a ViewController being pushed in the viewDidAppear:
///
/// FUNCTION - VIEW DID APPEAR
///
override func viewDidAppear(_ animated: Bool) {
// GET VIEW CONTROLLER
let secondViewController = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController") as! secondViewController
// PUSH VIEW ONTOP OF CURRENT NAVIGATION CONTROLLER
self.navigationController.pushViewController(secondViewController, animated: true)
} // END - FUNCTION