Conversation
ADD support for animated flag ADD started with writing Snapshot test - WIP ADD Snapshot for Snapshot test, but current snapshot is not waht we want
ADD snapshot for tests MOD made scroll into view test work
…endent of snapshot check
MOD changed behavior of scrollToViewController to respect contentSize as scroll limit
Tests/StackClusterTests.swift
Outdated
let targetVC = childStack?.childViewControllers[2] | ||
|
||
// TODO: WIP stack layout is not as expected | ||
it("a target should be able to be centered") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MP0w @ChristianOrgler Can someone please take a look? It seems like the nested StackView has messed up sizes, that's why scrolling fails.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in which test? can't you take a look yourself? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChristianOrgler I looked into it and for me it seems like the StackViewController has a bug in this nested case or my test setup might need some adaptions to set the scroll views size correctly. It would cost me too much time to fix the StackViewController and is not in the scope of the scroll to ViewController
feature.
SwiftLint found issuesWarnings
Errors
Generated by 🚫 Danger |
Codecov Report
@@ Coverage Diff @@
## master #19 +/- ##
==========================================
- Coverage 99.33% 97.69% -1.64%
==========================================
Files 9 9
Lines 300 347 +47
==========================================
+ Hits 298 339 +41
- Misses 2 8 +6 Continue to review full report at Codecov.
|
@@ -9,6 +9,12 @@ | |||
import UIKit | |||
import SnapKit | |||
|
|||
public enum RelativePosition { | |||
case beginning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add some docu what does this actually mean and how it behaves if there is nothing to scroll or not able to scroll the child to the very top (or left) depending on the orientation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, public stuff should always have documentation
@@ -24,7 +30,7 @@ final class StackViewController: UIViewController { | |||
self.preserveParentWidth = configuration.preserveParentWidth | |||
|
|||
super.init(nibName: nil, bundle: nil) | |||
|
|||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
try to avoid these next time ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we removed trailing space from swiftlint and @AndreasThenn has a different Xcode setting
/// - relativePosition: An optional parameter to specify the alignement of the target. Depending of | ||
/// the stackViews' orientation some relativePosition values have different meaning: | ||
/// beginning - top/left, end - bottom/right | ||
public func scrollToViewController(target targetViewController: UIViewController, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not a really swifty naming.
I would suggest something like
func scroll(to viewController: UIViewController, at position: RelativePosition = .center, animated: Bool = true)
which results in the usage as
stack.scroll(to: child, at: .beginning)
- and why is the default not animating it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChristianOrgler I will adapt it, to use your naming, thx.
Why should the default be to animate it? - The only reason for no animation for me was to be more defensive: Animation is additional functionality. I am happy to set it to true
if there's a valid reason.
public func scrollToViewController(target targetViewController: UIViewController, | ||
position relativePosition: RelativePosition = .center, animated: Bool = false) { | ||
|
||
guard self.childViewControllers.contains(targetViewController) else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no self.
needed
if stackView.axis == .vertical { | ||
// keep current horizontal offset | ||
targetOffset.x = -scrollView.contentInset.left | ||
let topOffset = targetFrame.origin.y - scrollView.contentInset.top |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the calculation in the respected case
if targetOffset.y < scrollView.contentInset.top { | ||
targetOffset.y = scrollView.contentInset.top | ||
} | ||
targetOffset.y = ensureRangeFor(value: targetOffset.y, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is already done above right??
upperLimit: verticalLimit) | ||
} else { | ||
// keep current vertical offset | ||
targetOffset.y = -scrollView.contentInset.top |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why negative?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChristianOrgler I used the contentInset, there a positive value would move the scroll view out of the view. I improved the code now, by using the actual currentOffset.
} else { | ||
// keep current vertical offset | ||
targetOffset.y = -scrollView.contentInset.top | ||
let leftOffset = targetFrame.origin.x - scrollView.contentInset.left |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as vertical, move it in the cases
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChristianOrgler Why? I then I need to duplicate code, that I need in multiple cases.
Tests/StackClusterTests.swift
Outdated
expect(vc).to(haveValidSnapshot()) | ||
} | ||
|
||
it("a leftmost target gets left aligned, if it could not get right aligned correctly") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't get this naming ^^
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
me too, please use something like:
context when ....... (if needed)
it should.....
Tests/StackClusterTests.swift
Outdated
let targetVC = childStack?.childViewControllers[2] | ||
|
||
// TODO: WIP stack layout is not as expected | ||
it("a target should be able to be centered") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in which test? can't you take a look yourself? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please add a general description of the PR? What is it for? Is this to address deep linking?
I feel like this is a specific scenario of the issue how to show/navigate to a component
which we try to address with deep linking, but not sure. In that case, we should maybe address this in a different way which is more generic to all Components and thus tends to an API which is easier to use and understand.
Don't get me wrong, but I don't really see how adding this public method in the StackViewController
will ease the usage of the library (maybe there is something I am missing or overlooking and thus a general description of the PR would help).
ps: also as general rule of thumb, we should always try to avoid a significant decrease on code coverage and public documentation.
return | ||
} | ||
|
||
var targetOffset = CGPoint(x: 0, y: 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or CGPoint.zero
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or .zero
;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChristianOrgler .zero
does not work, var
needs a contextual type.
@@ -69,4 +75,80 @@ final class StackViewController: UIViewController { | |||
stackView.addArrangedSubview(childViewController.view) | |||
childViewController.didMove(toParentViewController: self) | |||
} | |||
|
|||
/// A method to access the scrollView from outside and scroll to a given target ViewController. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is not really accessing the scroll view from outside, is it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joanromano Why not? The stackView is private and we want to access its scrolling functionality via code from the app. Its not complete access if that's your concern, I will then use a better description.
scrollView.backgroundColor = configuration.backgroundColor | ||
} | ||
|
||
required init?(coder aDecoder: NSCoder) { | ||
required init?(coder _: NSCoder) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverted, was due SwiftFormat
@@ -48,13 +54,13 @@ final class StackViewController: UIViewController { | |||
scrollView.alwaysBounceVertical = false | |||
scrollView.showsHorizontalScrollIndicator = false | |||
|
|||
scrollView.snp.makeConstraints { (make) in | |||
scrollView.snp.makeConstraints { make in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why these 2 changes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@MP0w sorry, I have a code format plugin installed. (https://github.com/nicklockwood/SwiftFormat)
I tried to only use it on my code, but I case I used it on the whole file.
In our codebase we would remove the brackets, if they are not needed. I can revert these changes, if you want to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverted, was due SwiftFormat
@@ -9,6 +9,12 @@ | |||
import UIKit | |||
import SnapKit | |||
|
|||
public enum RelativePosition { | |||
case beginning |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, public stuff should always have documentation
targetOffset.x = ensureRangeFor(value: targetOffset.x, | ||
lowerLimit: scrollView.contentInset.left, | ||
upperLimit: horizontalLimit) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had a general look here and also seems to me that some ensureRangeFor
calls are not needed.
Tests/StackClusterTests.swift
Outdated
} | ||
} | ||
|
||
context("when scrolling horziontally in a vertical nested stack") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
horizontally
@@ -161,7 +163,7 @@ class StackClusterTests: QuickSpec { | |||
it("should be able to scroll horizontally") { | |||
let vc = nestedStack() | |||
vc?.loadViewIfNeeded() | |||
|
|||
vc?.view.layoutIfNeeded() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need those layoutIfNeeded
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joanromano I added it to every testcase, that would fail, if the check with haveValidSnapshot
was not called. Checking for a snapshot forces the layout, but if you remove the check and then just check for expect(scrollView).to(scroll(.horizontal))
the test would fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so why is it added to some tests which have expect(vc).to(haveValidSnapshot())
as the first check? Sorry I may not understand you 😕
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably because you ask to load the view but nothing triggers a layout. You don't need to load the view before doing a snapshot and you don't need to check here if it can scroll since there are already tests for this (if I missed some than add the tests cases but don't mix it with these unrelated specs)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the reason was that nothing is triggering a layout action (because it's not embeded somewhere) - therefore the layoutIfNeeded is needed here, right @AndreasThenn ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ChristianOrgler is right. @joanromano @MP0w you don't need to load the view to take snapshots, that's true. But you have three expectations: expect(vc).to(haveValidSnapshot()) expect(scrollView).to(scroll(.horizontal)) expect(scrollView).toNot(scroll(.vertical))
In my opinion these expectations should be independent and the order should not matter. Having to check haveValidSnapshot() should not be a precondition for the other two expectations.
you don't need to check here if it can scroll
I did not add these, I just fixed that they will succeed, even without checking for a snapshot.
We need some explanation why we want to add this, because currently this StackVC can't be accessed from user of Matrioska AFAIK (they just get a Component) so this method can't even be used... it's hard to understand why we want to add this |
After we got our AppStructure built we have all the ViewControllers we need. In our case, a |
@AndreasThenn afaik and as @MP0w said But also, I think a specific use case should not drive the implementation. I would expect Matrioska to support navigation to some I believe we should let all the |
@joanromano @MP0w Yes this is driven by the fact that we need this for deep linking – when we know where we want, we somehow want to highlight and scroll to a specific child. I'm on the idea to have a generic way of doing this. E.g. a protocl We could make the What do you think? |
@ChristianOrgler afaik clients should, ideally, only know about |
@joanromano but this needs to be available on run-time right? So how does a client access the component? The Can you give me a more concrete example on how the flow should look like form your point of view? |
@ChristianOrgler maybe I am missing something but a client has normally access to the root Not quite sure what you mean by |
Currently I don't see a possibility to access the But when a user taps on a view which may redirect him to another view in another tab or so, I need to be able to call a method on the Tabbar-VC to do so. (or the StackView in that case) Can you give me an example how this should be done right now? |
Why? e.g: https://github.com/runtastic/Matrioska/blob/master/Example/MatrioskaExample/AppDelegate.swift There the root and all his child components are accessible aren't they?
True, but I don't think it makes sense to do this on a VC basis. If we provide more custom VCs to provide more out of the box Clusters (say, for instance, a hamburger menu cluster) we would always have to have different APIs for the different VCs, making it really hard and confusing to use. We should do this on a generic I believe there must be a way to do this, e.g we should create a protocol/method that is optionally opt-in for Components in order to navigate/show a child by a given id.
Which then Stack or TabBar clusters would implement differently inside. Sorry I have no time for a proper implementation. If you guys really think this is not possible or makes no sense maybe you are right, but from the PR seems like is not really correct conceptually speaking what is done here, since this is only a new method added to the StackVC just to serve a specific use case from the whole issue, but not useful for the library at all (I can't create nested clusters and use this for instance, or the StackVC itself is internal so is not even accessible). As said got no time now for a real implementation but I will give it a try later on. |
@joanromano as I said "I'm on the idea to have a generic way of doing this. E.g. a protocl Navigate ?" I also think we need a more generic approach to this. Open up the StackVC and Tabbar and whatever we provide here and create a protocol for this. 👍 @AndreasThenn can you introduce a protocol with proper naming and implementation in this PR? |
MOD improved naming of StackClusterTests REM old test images ADD new snaphot images to fit new naming
ADD protocol Focusable to UITabBarController and StackViewController
@ChristianOrgler I updated my PR and introduced Focusable as protocol. No tests yet, but I want to wait for feedback first. |
MOD made scroll method internal
FIX StackViewController now keeps stackView height when horizontal aligned, this improves nested views. REM old reference images MOD renamed StackClusterTests
The build bot does not pick up the SwiftLint configuration file in the project? (complains about violations in |
Source/Protocols/Focusable.swift
Outdated
protocol Focusable { | ||
|
||
/** Try to focus on the given UIViewController, otherwise it fails silently */ | ||
@discardableResult |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@discardableResult
on a function with return type Void
is kind of pointless, wouldn't you agree?
Source/Protocols/Focusable.swift
Outdated
|
||
/** Try to focus on the given UIViewController, otherwise it fails silently */ | ||
@discardableResult | ||
func focus(on viewController: UIViewController) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I second @joanromano's position: "I would expect Matrioska to support navigation to some Component or child Component in a generic way". Matrioska is basically a "Component
tree API", so the API should allow clients to focus Component
s, not UIViewController
s.
But before that, I think we need to introduce an identity concept for Component
s, a means by which Component
s can be looked up, so that you could find the Component
to focus.
The question is also whether the focus request should be dispatched to the root of the Component
tree, i.e.
root.focus(componentId: someId)
or the Component
to focus:
if let someNestedComponent = root.find(by: someId) {
someNestedComponent.focus()
}
To me it looks like the focusing will require a top-down approach anyway (stuff higher up the hierarchy potentially needs to be scrolled into position before focusing a nested child), so the first alternative seems the more natural thing to do.
As discussed offline, we will implement this in the app and not in Matrioska. |
No description provided.