Disable scroll for PDFView inside NSCollectionView - objective-c

I have a PdfView inside a CollectionView. Since both have its own scrollviews I have conflicts in scrolling. So i want to disable scrolling for PdfView. How can I do it?

My solution is a little rough, but does work.
I only wanted to stop horizontal scrolling on my PDFViews - my collection view scrolls horizontally.
I made a view that selectively filters out the scroll mouse events and put it over the PFD view.
class HorizontalScrollBlockerView: NSView
{
var scrollNextResponder: NSResponder?
override func scrollWheel(with event: NSEvent) {
guard scrollNextResponder != nil else {
return
}
if fabs(event.deltaX) >= fabs(event.deltaY) {
scrollNextResponder!.scrollWheel(with: event)
} else {
super.scrollWheel(with: event)
}
}
}
I set the view's 'scrollNextResponder' to be the superView of the PDFView.
I also wrote a method that gets the first child scroll view (enclosedScrollView) which makes sure the PDFView is solid in the horizontal axis when correctly scaled.
if let scrollView = pdfView.enclosedScrollView {
scrollView.usesPredominantAxisScrolling = true
scrollView.hasHorizontalScroller = false
scrollView.horizontalScrollElasticity = NSScrollView.Elasticity.none
}

For a regular Scroll View you can remove scrolls by setting horizontal & vertical scrolls to false value. So for a PDF view try this :
NSScrollView *enclosingScrollView = [myPdfView enclosingScrollView];
[enclosingScrollView setHasHorizontalScroller:NO];
[enclosingScrollView setHasVerticalScroller:NO];

Related

TextField Covered By Software Keyboard And Doesn't Scroll Into View

When using Compose with a TextField at the bottom of the screen, when I focus the TextField, the software keyboard is opening up and covering the TextField, and it is not scrolling the TextField into view.
I am using accompanist and have things set up so that it could scroll into view, but it is not doing it automatically.
I also found that if the keyboard is already open, and you focus a TextField that is scrolled off screen, it does automatically scroll it onto screen. So it sort of seems like the behavior is there, but it just isn't working correctly because the focus happens before the keyboard opens.
Does anyone have a good solution to make the TextField scroll into view when the software keyboard opens?
Edit:
Simple Example:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setContent {
ProvideWindowInsets {
Column(
Modifier
.statusBarsPadding()
.navigationBarsWithImePadding()
.verticalScroll(rememberScrollState())
) {
val focusManager = LocalFocusManager.current
(0..20).forEach {
var test by remember { mutableStateOf("") }
TextField(
test,
{ test = it },
label = { Text(it.toString()) },
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
keyboardActions = KeyboardActions(onNext = {
focusManager.moveFocus(FocusDirection.Next)
})
)
}
}
}
}
}
}
With the keyboard closed, if I click a TextField near the bottom, it gets focus, then the keyboard pops up and covers it. What I want to happen is after the keyboard pops up, the TextField should scroll into view.
However if I then hit the Next button on the keyboard, it will go to the next TextField and scroll it into view. Which is what I want.
I had a similiar issue. This seems to be a bug in Compose. To fix it, add the following in your activity's onCreate:
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)

RecyclerView custom LayoutManager remove unwanted views

I have a custom LayoutManager (inherited from LinearLayoutManager) that needs to calculate the item width of each child and remove all children from RecyclerView that has no space for them to appear.
Sample code (edited V2):
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
super.onLayoutChildren(recycler, state)
// skip if orientation is vertical, for now we only horizontal custom menu
if (orientation == RecyclerView.VERTICAL) return
// skip if adapter has no items
if (itemCount == 0) return
var totalItemWidth = 0
var totalItemsCanFit = 0
// calculate menu item width and figure out how many items can fit in the screen
for (i in 0 until childCount) {
getChildAt(i)?.let { childView ->
totalItemWidth += getDecoratedMeasuredWidth(childView)
}
if (screenWidth > totalItemWidth) {
totalItemsCanFit++
}
}
// if all items can fit, do nothing and show the whole menu
if (childCount > totalItemsCanFit) {
// remove child views that have no space on screen
for (i in childCount - 1 downTo totalItemsCanFit) {
removeAndRecycleViewAt(i, recycler)
}
}
}
I have 2 questions:
Is the sample code above the correct way to approach this problem?
How can I add a 3-dot icon at the end after seeing that not all items could fit?
EDIT:
To clarify, what I am trying to achieve is a popup menu backed by a RecyclerView. The menu has no item limit, instead it should calculate each item width and remove all items that have no space. Also, add a 3-dot menu item as a more option at the end.
Regarding your first question:
See if addDisappearingView(View child) could help you,
according to the documentation:
To be called only during onLayoutChildren(Recycler, State) to add a
view to the layout that is known to be going away, either because it
has been removed or because it is actually not in the visible portion
of the container but is being laid out in order to inform RecyclerView
in how to animate the item out of view.
As for the second question - you simply need to implement a 'load more' feature to your recyclerView. How you'll implement this is up to your needs/design (if you want a button or auto-scroll...).
There are many tutorials for that, for example: https://androidride.com/android-recyclerview-load-more-on-scroll-example/ .

My popover view controller dissapears when NSStatusBar IconView image changes

I have a status bar app which shows a green circle in the status bar and alternates to a red circle every 10 seconds. It does this by using item.view = icon1; and item.view = icon2; to change the image. Initialised like this:
let item = NSStatusBar.systemStatusBar().statusItemWithLength(-1);
self.icon1 = IconView(imageName: "icon1", item: item);
self.icon2 = IconView(imageName: "icon2", item: item);
When you click on the green or red circle, a popover view with some settings I have made appears fine.
The problem is at each 10 second interval when the item.view changes to either the red or the green, the popover view closes and requires the user to click the green or red button again to show it.
How can I make the popover persist though status bar image changes and only disappear once the user clicks the red or green button again?
Here is my awakeFromNib() for the popover view:
override func awakeFromNib()
{
let edge = NSMinYEdge
let icon = self.icon
let icon2 = self.icon2
let icon3 = self.icon3
let rect = icon.frame
let rect2 = icon2.frame
let rect3 = icon3.frame
icon.onMouseDown = {
if (icon.isSelected)
{
self.popover?.showRelativeToRect(rect, ofView: icon, preferredEdge: edge);
return
}
self.popover?.close()
}
icon2.onMouseDown = {
if (icon2.isSelected)
{
self.popover?.showRelativeToRect(rect2, ofView: icon2, preferredEdge: edge);
return
}
self.popover?.close()
}
icon3.onMouseDown = {
if (icon3.isSelected)
{
self.popover?.showRelativeToRect(rect3, ofView: icon3, preferredEdge: edge);
return
}
self.popover?.close()
}
}
}
You have the popover attached to a view that is later removed from the view hierarchy. Instead of assigning a new view to NSStatusItem each time, use a single view and change it's properties to show different states (i.e. change it's image property).
Alternatively you can create an empty NSView and assign it to NSStatusItem view property. Then you can add and show your icons inside that view as needed. Make sure to attach the popover to this empty view instead of individual icons.
Put a breakpoint inside the popover class dealloc method to see the stack trace. You'll get a hint of why this is happening from that stack trace.

Zoom Flickable's content on wheel event

By default, Flickable scrolls on mouse wheel event: horizontally if Shift modifier is pressed, and vertically if no modifier is active. I'd like to override this behaviour to make it zoom in/out.
I tried to use MouseArea as a child of Flickable. Defining desired behaviour inside onWheel handler I get what I want, but it breaks flicking feature. Motion of two touch points is recognised as a wheel event on my Mac, what prevents Flickable to steel this event (MouseArea.preventSteeling is false by default). So I get somewhat mixed zooming feature that respects velocity/acceleration behaviour of Flickable.
Add an onWheel handler underneath your MouseArea:
MouseArea
{
onWheel: {
if (wheel.modifiers & Qt.ControlModifier){
if (wheel.angleDelta.y > 0)
{
zoomin()
}
else
{
zoomout()
}
}
else{
wheel.accepted=false
}
}
}

How to toggle visibility of NSSplitView subView + hide Pane Splitter divider?

We have a parent Split view (NSSplitView), and two subviews, Content and SideBar (the sidebar is on the right).
What would be the optimal Cocoa-friendly way to toggle the SideBar view?
I would really love it, if the suggested solution includes animation
I really don't need any suggestions related to external plugins, etc (e.g. BWToolkit)
HINT : I've been trying to do that, but still I had issues hiding the divider of the NSSplitView as well. How could I do it, while hiding it at the same time?
Here's a pretty decent tutorial that shows how to do this: Unraveling the Mysteries of NSSplitView.
Hiding the divider is done in NSSplitView's delegate method splitView:shouldHideDividerAtIndex:.
You will have to animate the frame size change yourself if you don't like the way NSSplitView does it.
Easiest way to do it is as follows - and it's animated: [SWIFT 5]
splitViewItems[1].animator().isCollapsed = true // Show side pane
splitViewItems[1].animator().isCollapsed = false // hide side pane
I wrote a Swift version of the content in the link from #Nathan's answer that works for me. In the context of my example splitView is set elsewhere, probably as an instance property on an encompassing class:
func toggleSidebar () {
if splitView.isSubviewCollapsed(splitView.subviews[1] as NSView) {
openSidebar()
} else {
closeSidebar()
}
}
func closeSidebar () {
let mainView = splitView.subviews[0] as NSView
let sidepanel = splitView.subviews[1] as NSView
sidepanel.hidden = true
let viewFrame = splitView.frame
mainView.frame.size = NSMakeSize(viewFrame.size.width, viewFrame.size.height)
splitView.display()
}
func openSidebar () {
let sidepanel = splitView.subviews[1] as NSView
sidepanel.hidden = false
let viewFrame = splitView.frame
sidepanel.frame.size = NSMakeSize(viewFrame.size.width, 200)
splitView.display()
}
These functions will probably methods in a class, they are for me. If your splitView can be nil you obviously have to check for that. This also assumes you have two subviews and the one at index 1, here as sidePanel is the one you want to collapse.
In Xcode 9.0 with Storyboards open Application Scene select View->Menu->Show sidebar. CTRL-click Show Sidebar, in sent actions delete the provided one, click on x. From the circle CTRL drag to First Responder in application scene and select toggleSideBar to connect to. Open storyboard and select the first split view item and in attributes inspector change behaviour from default to sidebar. Run and try with view menu item show/hide. All done in interface builder no code. toggleSideBar handles the first split view item. https://github.com/Dis3buted/SplitViewController
I got some artifacts with the code above, likely because it was out of context. I am sure it works where it was meant to. Anyway, here is a very streamlined implementation:
// this is the declaration of a left vertical subview of
// 'splitViewController', which is the name of the split view's outlet
var leftView: NSView {
return self.splitViewController.subviews[0] as NSView
}
// here is the action of a button that toggles the left vertical subview
// the left subview is always restored to 100 pixels here
#IBAction func someButton(sender: AnyObject) {
if splitViewController.isSubviewCollapsed(leftView) {
splitViewController.setPosition(100, ofDividerAtIndex: 0)
leftView.hidden = false
} else {
splitViewController.setPosition(0, ofDividerAtIndex: 0)
leftView.hidden = true
}
}
To see a good example using animations, control-click to download this file.
If your NSSplitView control is part of a NSSplitViewController object, then you can simply use this:
splitViewController.toggleSidebar(nil)