The problems lies way beyond ‘simultaneous or exclusive’ and is in my opinion a serious bug
with a lot of side effects and inconsistencies.
You only need one single view with one single drag gesture attached to it!
Set ‘minimumDistance: 0’ so the gesture gets recognized right away (like at touchBegan/touchDown
of a tap gesture) and let the .onChange(), .onEnded() and .updating() closures print something to the
console for checking when they fire.
Now start dragging (first gesture) and while doing so accidentally touch/tap/press the screen with another
finger. The ‘new’ (second) gesture does NOT get reported to you by the system! No callback gets fired…
HOWEVER, your original first gesture gets killed, which means no more updates, changes and - BIG BUMMER -
no .onEnded callback!!! And as there is no .onCancel callback like in UIKit, this behavior leaves you with
dangling gestures and every state machine that you tried to establish with pairs of ‘started-ended‘ gets
screwed up big time.
When you then place yet another ‘new‘ (third) gesture, a fresh drag gesture gets spun up (reporting all
callbacks like it’s supposed to) but being totally ignorant of the two former gestures, regardless of whether
you lifted the finger of your first and second gesture or one/both fingers are still on the screen !!!
And that is the reason why drag gestures (a one finger gesture) don’t work simultaneously with 2 finger
gestures like magnification or rotation. Because the second finger that touches the screen kills the
drag gesture but magnification and rotation start and keep on firing…
Attach more drag gestures to the same view, they all keep firing simultaneously without any problem
whatsoever. Any combination and number of rotations/magnifications - same correct behavior…
But put a drag gesture in the mix and the whole thing brakes!
That is for sure not as it’s supposed to work…
Post
Replies
Boosts
Views
Activity
ADDENDUM:
One thing that would clarify this problem, however not solve it, is the fact that a plain
• myWebview.load(URLRequest)
is using Mozilla/5.0 as the agent, which is confirmed by the server log.
However
• URLSession.shared.dataTask(with: URLRequest) { data, response, error in ... }
uses a CFNetwork executionExtension as the agent, also confirmed by the server log.
So if the executionExtension spins up its own URLSession that writes the cookies to
the cookieStore, it could be that after the dataTask’s completion handler the session
gets deallocated and hence the temporary session cookies get removed from the
cookieStore right away.
The question then is, how do you get a copy of those short-lived cookies before the
extension closes, because even
• myWebview.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in ...}
is asynchronous and too slow to grab hold of them. And even when
loading the response’s mime ‘text/HTML’ payload with
• myWebview.loadHTMLString()
right in the completion handler, it is too slow to find the cookies it would
need to e.g. attach to an ajax request in the onLoad() function of the
loaded HTML page.
Not to mention the general out-of-sync situation of all the forms in the
HTML that rely on corresponding tokens stored in cookies in order to
make POST requests etc.
———————————————————
Hi Quinn,
I’ve been tracing the cookie issues for over a month now and I am
able to reproduce and remedy all test cases 100%.
I do have a dedicated test environment with utility buttons in the UI
that let me e.g. log the cookieStore, empty the cookieStore etc.
All WKWebview delegate methods and cookieStore observers are
in place and log to the console when they fire.
So the 2 take away test scenarios for you are these:
———————————————————
SCENARIO 1:
———————————————————
• starting the test environment
• totally resetting the cookieStore by deleting all cookies
• logging the cookieStore shows that it is empty
• quitting the test environment
• re-loading the test environment
• environment is loading a test webpage from the internet with:
— myWebview.(URLRequest)
• the page loads correctly
• the response-header’s ‘Set-Cookie’ field requests 3 cookies to be set
• the WKWebview sets 3 cookies (as requested in the response-header’s ‘Set-Cookie’ field)
• the didChangeCookieStore callback fires 3 times
• logging the cookieStore shows that there are 3 cookies
• the 3 cookies are:
— 1 permanent cookie (with expiration date set) and
— 2 temporary session cookies (without expiration date set) - [csrfToken, CAKEPHP] tokens
———————————————————
Problems:
———————————————————
NONE !!!
———————————————————
Observations:
———————————————————
Unfortunately the logged response header DOESN’T display the ‘Set-Cookie’ field !!!
===== WK-WEB-VIEW: decidePolicyFor navigation RESPONSE
WKNavigationResponse: 0x160a13df0; response = NSHTTPURLResponse: 0x160a1a990
{ URL:https://testURL }
{ Status Code: 200, Headers {...}}
———————————————————
SCENARIO 2:
———————————————————
• totally resetting the cookieStore by deleting all cookies
• logging the cookieStore shows that it is empty
• quitting the test environment
• re-loading the test environment
• environment is loading a test webpage from the internet with
— URLSession.shared.dataTask(with: URLRequest) { data, response, error in ... }
• the response-header’s ‘Set-Cookie’ field requests 3 cookies to be set
• the didChangeCookieStore callback fires 3 times
• logging the cookieStore shows that there is only 1 cookie
• the 1 cookie is:
— 1 permanent cookie (with expiration date set)
— NONE of the 2 expected temporary session cookies (without expiration date set) have been set - [csrfToken, CAKEPHP] tokens
———————————————————
Problems:
———————————————————
Of course, if you now load the mime ‘text/HTML’ payload
(by casting the response’s data object into a string) with
• myWebview.loadHTMLString()
all user actions on that HTML page like e.g. triggering hidden requests
in POST forms that rely on the [csrfToken, CAKEPHP] tokens etc., are
denied by the server because of the missing cookies.
Equally, if there is e.g. an additional ajax HTTP request in this HTML’s
onLoad() function, I can see in the server logs, that this additional request
comes without the 2 [csrfToken, CAKEPHP] session cookies attached
(only the single permanent cookie is present) – and so the server re-sends
a new pair of [csrfToken, CAKEPHP] cookies with the new response.
As this ajax request/response only updates some minor data in the UI, but
the DOM is still the the same from the initial page request, you can imagine
that the new [csrfToken, CAKEPHP] cookies do not match up with all the
references in the HTML either, because of ‘token mismatches’.
The page is still broken!
———————————————————
Observations:
———————————————————
As the problems only seem to affect temporary session cookies maybe the bug
trace should focus on that area. Remember - every scenario was started with a
completely wiped out empty cookie store!
———————————————————
Fortunately the logged response header DOES display the ‘Set-Cookie’ field !!!
===== WK-WEB-VIEW: decidePolicyFor navigation RESPONSE
RESPONSE: NSHTTPURLResponse: 0x110172520 { URL: https://testURL } { Status Code: 200, Headers {
"Set-Cookie" = (
"CAKEPHP=bjhbajljh9f78vm9mko836kk2h; path=/; secure; HttpOnly",
"interfaceLanguage=deu; expires=Sat, 15-May-2021 16:21:10 GMT; Max-Age=2591999; path=/",
"csrfToken=591d618d10632e05f1aa79e003e82a6c5da73781f55466782885...; path=/"
);
} }
———————————————————
Miscellaneous:
———————————————————
When the navigationAction.didFinish delegate fires, thecookieStore isn’t
properly set yet, despite the didChangeCookieStore observerver having
fired long before.
You have to wait about 1.75 seconds in the best case scenario, but often
up to 2.5 seconds. So right now 3 seconds seems to be a safe setting.
I would rather expect the didFinish delegate to fire when even the
asynchronous cookieStore is in a valid state, or at least have an additional
delegate method for when really everything is completed...
Needless to say, that I did all cookieStore checks manually with a utility
button in my test environment UI, waiting at least 5 seconds to be sure
that all asynchronous tasks have finished.
———————————————————
REMEDIES:
———————————————————
One remedy for the missing cookie issue is to stash away the ‘Set-Cookie’
header from the initial request, extract the tokens from the signature,
instantiate some custom cookies with the respective values and store
them into the cookieStore.
But that is a little bit of a management nightmare!
———————————————————
GENERAL OBSERVATION AND
ENHANCEMENT REQUEST
———————————————————
Unfortunately the system doesn’t inform you when the server responds with a
REDIRECT 302 status. Neither with a decidePolicyFor navigation RESPONSE
nor with a dedicated callback. The system rather decides to issue a new request
automatically and you only find yourself with an additional invocation of
decidePolicyFor navigation ACTION wondering where that came from,
because there is no mention of any status 302 in the whole processing and
logging chain.
If I hadn’t had access to the server logs I would have never known about the
302, and kept on wondering, what in my code triggers this additional request.
However, I have seen in the WKNavigationAction.h file that there is a property
called ’isRedirect’ just along with ‘navigationType’ etc. that one can access via
• navigationAction.value(forKeyPath: "isRedirect")
In the case of the automatic REDIRECT that the system performs, this value is
set to 1 for the following decidePolicyFor navigation ACTION.
It would be really helpful if the navigationAction.description would make this
information available along with
• WKNavigationAction: 0x112076910;
— navigationType = -1;
— syntheticClickType = 0;
— position x = 0.00 y = 0.00
— isRedirect = 1
— request = ...
without having to explicitly poll for it.
———————————————————
Thanks for clarifying.But that still leaves us with one (unanswered) question.In the Playgrounds app on the iPad, I am allowed to create newriders in the tab bar. This opens up a file that is shared between allmodules and pages of the very same Playground - and I thinkeven cross-playground.So, why can I create this new file and - if it is sandboxed - why doesit appear in my playground's project bundle as a dedicated filein the Files App and why can I copy/open it from here into everyother Application? To me it seems to be in the public domain.So if I can share code between several Playgrounds/Modules/Pagesshouldn't I also be able to share a file that my code creates to the verysame folder in the project bundle to make it available on the next pageor for another Module/Playground. Isn't this the whole point of makingsharing public in the Playgrounds App?So the big question is - what is this URL in the project file to write mycomposed file to, like I do when creating a new code-file in the project?
Hello Quinn,been watching WWDC and your sessions since 2010, thanks for theclarity of your performances...I am having a similar concern here but rather related to iOS playgrounds.I am using Playgrounds simply to prototype and test certain functionalityof my Apps and I needed to store a file that was created by my code(myRecorded.wav) to some URL on the iPad where it is accessible for otherApps. So, somewhere in the Playground Container File or even in theFiles App.Is there a publicly shared directory on iOS Playgrounds and what wouldthe URL be which I had to store my 'myRecorded.wav' file into? Or the URLin the Files App for that matter?Thanks in advance for your help!