Posts

Post marked as solved
7 Replies
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 not yet marked as solved
29 Replies
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. ———————————————————
Post not yet marked as solved
29 Replies
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. ———————————————————
Post not yet marked as solved
6 Replies
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?
Post not yet marked as solved
6 Replies
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!
Post not yet marked as solved
2 Replies
Well, the only way to get around this problem is to select the voice that you think works best for your use case and then force your user to load the voice first in the settings app.You can do this by checking the return value of your init?(identifier: String) method. If it's nil the voice is not installed on the device yet and you can promt the user to download the voice first by stopping your voice output.The different intonations you can achieve by punctuation and other little tricks are not synthesizer specific but rather voice specific, what works on one voice may not on another voice. What works in one language won't in another.The only uncertainty that will still remain is that we don't know whether Apple is alternating or improving individual voices which might break your desired intonation.I haven't been able yet to get into contact with a voice engineer in order to find out whether there are updates or version numbers available for the voices and how often they change things there.