Sync two web views - swift

I have a need to sync two web views, so that anything that happens in one web view happens simultaneously in the other.
I have tried various ways, without success, and the more I try the more convoluted and likely bug ridden this is getting.
I feel there maybe a very simple way to do this, but can not figure it out.
One of the things that I note is not allowed is having the same NSTextField as the "takeStringURLFrom"
override func controlTextDidEndEditing(obj: NSNotification) {
webViewLeft.takeStringURLFrom(hiddenTextField)
webViewRight.takeStringURLFrom(urlField)
}
override func webView(sender: WebView!, didCommitLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
webViewRight.takeStringURLFrom(urlField)
webViewLeft.takeStringURLFrom(hiddenTextField)
printLn("realised just creating an infinite loop here")
}
}

I don't like this but it appears to work as needed. I think it needs some refinement. Using a hidden text field to mimic the url for the second web view and tie each web view to there respective text fields via the web view's referenced action "takeStringUrlFrom"
override func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!) {
if frame == sender.mainFrame {
urlField.stringValue = sender.mainFrameURL
hiddenTextField.stringValue = sender.mainFrameURL
window.makeFirstResponder(nil)
window.makeFirstResponder(hiddenTextField)
window.makeFirstResponder(nil)
window.makeFirstResponder(urlField)
window.makeFirstResponder(nil)
}
}

Related

Change region in UI testing for each case

i am using swift-snapshot-testing library to do snapshot testing. It's important for me to do all the tests with different localization to check all the UIs when the text is changed.
For example, the UI testing scheme is set to run on German language and region, In some tests, I want to see the english version as well and I did that
class BackgroundViewFactoryTests: XCTestCase {
override class func setUp() {
SnapshotTesting.diffTool = "ksdiff"
}
func test_backgroundView_contact_eng() {
continueAfterFailure = false
XCUIApplication().launchArguments += ["-AppleLanguages", "(en)"]
XCUIApplication().launchArguments += ["-AppleLocale", "en_UK"]
XCUIApplication().launch()
let view = BackgroundViewFactory()
view.backgroundColor = .darkGray
assertSnapshot(matching: view, as: .image, record: false)
}
}
But it's still not working and run on German language, could anyone give me hit?
Thanks
The snapshot tests should be agnostic to all Strings. You don’t want breaking tests if you change one translation.
Test the translation in a separate UnitTest.

NSDocument Tab Window Restoration

This question deals with tab window restoration in a document-based app.
In an OSX, document-based app, which allows a user to create and convert tab windows, I need to preserve and restore the 'tab' state of each window.
Currently, my document controller restores its documents windows, but not the tab deployment; I get back individual windows; I can merge all back into one, but this is too heavy-handed as their former groupings are lost.
My app document class's - makeWindowControllers() function is where I affect the new controllers, whether they should cascade, which I'd read be false, during restore:
// Determine cascade based on state of application delegate
controller.shouldCascadeWindows = <app did receive applicationWillFinishLaunching>
so it would be false until it's finished launching.
Finally, my window's class features methods:
override func addTabbedWindow(_ window: NSWindow, ordered: NSWindow.OrderingMode) {
super.addTabbedWindow(window, ordered: ordered)
window.invalidateRestorableState()
}
override func moveTabToNewWindow(_ sender: Any?) {
super.moveTabToNewWindow(sender)
self.invalidateRestorableState()
}
override func encodeRestorableState(with coder: NSCoder) {
if let tabGroup = self.tabGroup {
let tabIndex = tabGroup.windows.firstIndex(of: self)
coder.encode(tabIndex, forKey: "tabIndex" )
Swift.print("<- tabIndex: \(String(describing: tabIndex))")
}
}
override func restoreState(with coder: NSCoder) {
let tabIndex = coder.decodeInt64(forKey: "tabIndex")
Swift.print("-> tabIndex: \(tabIndex)")
}
to invalidate the window restore state when the tab state is changed. But I'm not sure with the NSWindowRestoration protocol implementation, who or what needs to implement the protocol when a document controller is involved.
I think this is the reason the last function is never called. I get debug output about the encoding but during the next app execution the restoreStore(coder:) function is never called.
So who implements this window restore protocol in such an environment I guess is my question, or a decent example doing so.
My question reveals you not require anything special for a document based app; I've updated my prototype which features this support and environment here SimpleViewer, which features Swift5, a document based app supporting tabs.

How to unit test a textView using a spy

I have a cell that contains a textView and i would like to test that the properties of that textView are set correctly using unit tests. However i seem to have hit a blocker when it comes to having access to the textView in the test since its private.
Is there a way I can test my textView:-
Here is my code
class MyCell {
private let myText: UITextView = {
let textView = UITextView()
textView.isScrollEnabled = false
textView.isEditable = false
return textView
}()
func setup(viewModel: MYViewModel) {
if viewModel.someValue {
myText.backgroundColor = UIColor.red
} else {
myText.backgroundColor = .clear
}
}
}
Is it possible to test something like the testView's background color is set to clear ?
Unless you want to relax the access level of myText from private to internal (the default if you don't specify it), there is no direct way to test it.
The only suggestion I have to test this indirectly would be to use snapshot testing.
You could write two snapshot tests, one for each value of .someValue from your MYViewModel.
Another option to make the testing –and maintainability– of your view straightforward is to introduce a ViewConfiguration value type, following the humble view pattern.
Basically, you can have a struct in between MYViewModel and MyCell that describes each of the view properties for MyCell.
struct MyCellViewConfiguration {
let textFieldBackgroundColor: UIColor
}
extension MYViewModel {
var viewConfiguration: MyCellViewConfiguration = {
return MyCellViewConfiguration(
textFieldBackgroundColor: someValue ? .red : .clear
)
}
}
extension MyCell {
func setup(with configuration: MyCellViewConfiguration) {
myText.backgroundColor = configuration.textFieldBackgroundColor
}
}
The code in setup(with configuration: MyCellViewConfiguration) is so simple –just a 1-to-1 assignment– that you can get away without testing it.
You can then write test for how MyCellViewConfiguration is computed from MYViewModel.
Simply remove private from your declaration. Then the access control will be the default internal. In the test code, make sure to #testable import so gain access to internal features.
Unit testing a few attributes isn't hard. But if you want a test that records the appearance, look into snapshot testing. This can be done without XCUITestCase. It's an order of magnitude slower than a regular unit test, but probably another order of magnitude faster than a UI test.

Returning a value from completion block/closure used in datasource method

I have a custom view which is going to be displayed in the collection view. View will be added in a stack view embedded in a view controller. A data source method is exposed to provide a view object.
Now here is the problem, before adding my custom view to stack I need to make a gateway call first to check if it is allowed to display the view or not.
As gateway call is asynchronous and returns me the response in the callback. Based on callback I need to instantiate the view and return in data source callback provided.
Here is demo code just for better understanding. Forgive me for my terrible naming conventions.
func customeViewInStack(customView: UIView) -> [UIView]? {
if viewIsAllowedToBeDisplayed {
self.downloadViewContent(onCompletionHandler: { contentDownloadSuccessfull in
if contentDownloadSuccessfull
// Return new instance of my custom view
else
// return nil
})
}
return nil
}
All the suggestions I read were about using a closure to pass the value, but due to data source method and callback in gateway call, I need to use old fashion return statement.
Edit 1
I have updated code snippet for better understanding.
I can not change function signature as it is part of a framework and a datasource method.
Also this method is shared between different features. What I mean by that is different views going to get added in stack view and they have their own conditional check wether to add them or not.
So basically what I am trying to achieve is until i do not get response from gateway operation, program execution should not proceed ahead.
I have tried using DispatchGroup class, but not able to achieve my goal.
Can someone suggest me on how should I tackle this problem
Thanks
About that, the correct solution is to not return something from the function, but instead give a completion to that function.
func customeViewInStack(customView: UIView, completion: #escaping ([UIView]?) -> Void) {
self.checkIfViewShouldBeShown(onCompletionHandler: { shouldDisplayView in
completion(shouldDisplayView ? [...(put the stack of views to return here))] : nil)
})
}
EDIT: I read it poorly first time, so I enhanced my answer.
As you need to pass it to collection view, I suggest that you will store it afterwards, and reload collection:
private var viewsForCollection: [UIView] = []
customeViewInStack(customView: UIView(), completion: { [weak self] views in
self?.viewsForCollection = views ?? [] // I guess optionality for array is unnecessary
self?.collectionView.reloadData()
})
At first, you will have an empty collection, but as soon as your views are ready, you can reload it.
You will need to declare your own completion block and use that to get your new view:
Here is what that declaration looks like:
// Declaration
func customeViewInStack(customView: UIView, completion: #escaping ([UIView]? -> ()) {
self.checkIfViewShouldBeShown(onCompletionHandler: { shouldDisplayView in
if shouldDisplayView
// Call the completion block with the views
completion(views)
else
// Call the completion block with nil
completion(nil)
})
}
And then in your usage of this, you would use it just like you used the checkIfViewShouldBeShown method:
customViewInStack(customView: customView, completion: { views in
guard let views = views else {
// Views are nil
}
// Do what you need to do with views
})

Observe a string and get from API with RxSwift

I have a MVVM test project to experiment RxSwift. I have a UItextfield a button. User write a food name, click on the button and a get from an API is triggered to get all recipes with that food.
View model
struct FoodViewModel
var foodIdentifier: Variable<String> = Variable<String>("")
init() {
foodIdentifier.asObservable().subscribe(onNext: { (identifier) in
self.getRecipes() // Get from API
})
}
}
ViewController
class FoodViewController: UIViewController {
#IBOutlet weak var foodTextField: UITextField!
#IBAction func setCurrentRace(_ sender: Any) {
viewModel.foodIdentifier.value = foodTextField.text!
}
}
After compile I got an error
Closure cannot implicitly capture a mutating self parameter
What I'm doing wrong ? I think it's because of struct of FoodViewModel. If yes, how can I achieve that using struct ?
-- EDIT
I wrote all of the below but forgot to answer your explicit question... The reason you are getting the error is because you are trying to capture self in a closure where self is a struct. If this were allowed, you would be capturing a copy of the view model that you haven't even finished constructing. Switching your view model to a class alleviates the problem because you are no longer capturing a copy, but the object itself for later use.
Here is a better way to set up a view model. You didn't give all the necessary information so I took some liberties...
First we need a model. I don't know exactly what should be in a Recipe so you will have to fill it in.
struct Recipe { }
Next we have our view model. Note that it doesn't directly connect with anything in the UI or the server. This makes testing very easy.
protocol API {
func getRecipies(withFood: String) -> Observable<[Recipe]>
}
protocol FoodSource {
var foodText: Observable<String> { get }
}
struct FoodViewModel {
let recipes: Observable<[Recipe]>
init(api: API, source: FoodSource) {
recipes = source.foodText
.flatMapLatest({ api.getRecipies(withFood: $0) })
}
}
In real code, you aren't going to want to make a new server call every time the user types a letter. There are a lot of examples on the web that explain how to build in a delay that waits until the user stops typing before making the call.
Then you have the actual view controller. You didn't mention what you wanted to do with the results of the server call. Maybe you want to bind the result to a table view? I'm just printing the results here.
class FoodViewController: UIViewController, FoodSource {
#IBOutlet weak var foodTextField: UITextField!
var api: API!
override func viewDidLoad() {
super.viewDidLoad()
let viewModel = FoodViewModel(api: api, source: self)
viewModel.recipes.subscribe(onNext: {
print($0)
}).disposed(by: bag)
}
var foodText: Observable<String> {
return foodTextField.rx.text.map { $0 ?? "" }.asObservable()
}
let bag = DisposeBag()
}
Notice how we avoid having to make an IBAction. when you are coding up a view controller with Rx, you will find that almost all the code ends up in the viewDidLoad method. This is because with Rx, you are mainly just worried about wiring everything up. Once the observables are wired up, user action will cause things to happen. It's more like programming a spreadsheet. You just put in the formulas and link the observables together. User's data entry takes care of the actual action.
The above is just one way of setting everything up. This method matches closely with Srdan Rasic's model: http://rasic.info/a-different-take-on-mvvm-with-swift/
You could also turn the food view model into a pure function like this:
struct FoodSink {
let recipes: Observable<[Recipe]>
}
func foodViewModel(api: API, source: FoodSource) -> FoodSink {
let recipes = source.foodText
.flatMapLatest({ api.getRecipies(withFood: $0) })
return FoodSink(recipes: recipes)
}
One takeaway from this... Try to avoid using Subjects or Variables. Here's a great article that helps determine when using a Subject or Variable is appropriate: http://davesexton.com/blog/post/To-Use-Subject-Or-Not-To-Use-Subject.aspx