SwiftUI macOS Disable Scroll Indicator Background - macos

I'm looking for a way to disable scroll indicator background(of a TextEditor) in SwiftUI, macOS to only show the moving part of the indicator(like in popovers) look at examples below:
What I currently have:
What I'm looking for:

Updated to include feedback from OP that original soln proposed doesn't quite completely remove tint.
On Ventura with Xcode 14 beta 1 it's possible to come very close [0] to achieving the desired effect by adding a background modifier after the .scrollContentBackground(.hidden) extends the background under the scrollbar,
e.g.
TextEditor(text: $text)
.scrollContentBackground(.hidden)
.background(.cyan)
Where $text is a little snippet of Under Milk Wood gives
Beyond this, afaik SwiftUI's TextEditor doesnt have native api to get closer at the moment. As an alternative though - if working with underlying AppKit components and its trade-offs is acceptable - then nicer styling might be an option.
For example using the Introspect library to enable the more modern overlay scrollerStyle:
import Introspect // from https://github.com/siteline/SwiftUI-Introspect
struct ContentView: View {
#State var text = demoText
var body: some View {
TextEditor(text: $text)
.scrollContentBackground(.hidden)
.background(.cyan)
.introspectTextView { (nsV: NSTextView) in
DispatchQueue.main.async {
nsV.enclosingScrollView?.scrollerStyle = .overlay
}
}
}
}
Seems to give quite a nice effect
[0] The subtle tint that remains, is as can be seen - missable - or at least was for me :-/

Related

Xcode Playground Timeline empty

I am trying to use the timeline assistant editor for playground in xcode version 7.3.1, it is always empty.
Timeline assistant editor
I think the error is from xcode, however from search it doesn't look like anyone got the same error so i am confused.
To display the result of print you need to open the "debug area" by going to menu
View > Debug Area > Show Debug Area
or click on the button in the lower left part:
To display the timeline graph, you could use XCPCaptureValue:
import XCPlayground
var x = 0
for i in 0...10 {
x += i
print(x)
XCPCaptureValue("Value for x", value: x)
}
but XCPCaptureValue has been deprecated and won't be available in the future (there's no available replacement).
The alternative is to display the graph inline by clicking on the "+" button on the right:
Do a right click on the graphs and you can choose to display value history instead:
I was just getting started in Playgrounds myself, and came across the same problem of not being able to print to Timeline.
This Medium article explains how to show or render things in the timeline as of Xcode 8 and Swift 3. Basically, you have to create a view and assign it to the PlaygroundPage.current.liveView:
import UIKit
import PlaygroundSupport
let contentView = UIView(frame: CGRect(x: 0, y: 0, width: 320.0, height: 600.0))
contentView.backgroundColor = UIColor.white
PlaygroundPage.current.liveView = contentView
Afterwards, you can add anything to your contentView to be displayed in the timeline. The PlaygroundPage.current.liveView can receive any UIView, such as UILabel, UITextField, etc.
However, sometimes the created view defaults to a black background, so you have to remember to set the .backgroundColor to UIColor.white to see it's info/child views.

Weird XCode 7 Interface Builder warnings

I have set font traits (bold, light) for several of my labels in an OSX app and now I get these warnings:
.../MainMenu.xib:9:
Xcode.IDEInterfaceBuilder.Cocoa.NSObject.BroadSystemFontWeights
without any explanation. Often the meant label isn't even selected when I click on the warnings. Can someone shed a light on what those warnings mean and how to get rid of them?
This is a warning shown starting from Xcode 7 when UI elements like a label or table view column header use a font style or variation that is not available on older OSes (and of course your project is still targeting them).
In my project a table view column header was using the system font with the "medium" font style variation instead of regular, in a project targeting OS X 10.9+.
The weird thing is I had to restart Xcode as Interface Builder refused to change the style of the control. Possibly a small glitch of this early 7.0.1 Xcode version.
Current answer almost gets it, but problem is not with font styles being unavailable in older targets, it's with Xcode not handling them properly, see full blog post for details.
If you want to keep your styles, use custom textfield with custom inspectable property. Open up identity inspector and set custom class to TextField, preferred font weight attribute will show up in attribute inspector, set the required value, build and enjoy the result.
import AppKit
#IBDesignable public class TextField: NSTextField
{
#IBInspectable public var preferredFontWeight: Int = 0
override public func awakeFromNib() {
if #available(OSX 10.11, *) {
return
}
guard
let weight: Int = self.preferredFontWeight where weight > 0,
let font: NSFont = self.font,
let name: String = font.familyName,
let manager: NSFontManager = NSFontManager.sharedFontManager() else {
return
}
// Full details here – https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSFontManager_Class/#//apple_ref/occ/instm/NSFontManager/convertWeight:ofFont:
//
// 1 – ultralight
// 2 – thin
// 3 – light, extralight
// 4 – book
// 5 – regular, display
// 6 – medium
// 7 – demi, demibold
// 8 – semi, semibold
// 9 – bold
// 10 – extra, extrabold
// 11 – heavy
// 12 – black
// 13 – ultrablack
// 14 – extrablack
if let font: NSFont = manager.fontWithFamily(name, traits: manager.traitsOfFont(font), weight: weight, size: font.pointSize) {
self.font = font
}
}
}
If you don't really care about the styles, use regular weight font for all text, it should solve the problem, see my earlier answer for available options.

What does NSObject.BroadSystemFontWeights warning message mean?

I recently updated XCode to 7.0 and I get this warning message:
Xcode.IDEInterfaceBuilder.Cocoa.NSObject.BroadSystemFontWeights
What does it mean and how do I get rid of it ?
I got the same error when I set the font weight to Semibold to a label with system font. This weight is available for the new system font (San Francisco) but not for the old Helvetica Neue, so I guess that that error means we won't get the right weight on older OS.
Changing the font to a weight available also for Helvetica Neue, Bold in my case, has fixed the error for me.
The problem is not with changing system font weight, the problem is with Xcode not handling this properly – contradicting statement, I know, see full blog post for details. There are three scenarios.
First – explicit typography is not important, regular weight is acceptable. Then stick with Marco's answer and use explicit regular weight.
Second – explicit typography is preferable, but can be compromised on older systems. This is the default behaviour right now, Xcode simply shows a warning and uses regular font on pre-10.11 targets. If you use adaptive layouts, everything should be fine. To get rid of the warning, you can simply set higher target in storyboard inspector:
Note, if your storyboard uses fallback features for earlier targets, they might become disabled, which will cause problems – I haven't come across any so far.
Third – explicit typography is a must. In this case you can use custom textfield with custom inspectable property. Open up identity inspector and set custom class to TextField, preferred font weight attribute will show up in attribute inspector, set the required value, build and enjoy the result.
import AppKit
#IBDesignable public class TextField: NSTextField
{
#IBInspectable public var preferredFontWeight: Int = 0
override public func awakeFromNib() {
if #available(OSX 10.11, *) {
return
}
guard
let weight: Int = self.preferredFontWeight where weight > 0,
let font: NSFont = self.font,
let name: String = font.familyName,
let manager: NSFontManager = NSFontManager.sharedFontManager() else {
return
}
// Full details here – https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSFontManager_Class/#//apple_ref/occ/instm/NSFontManager/convertWeight:ofFont:
//
// 1 – ultralight
// 2 – thin
// 3 – light, extralight
// 4 – book
// 5 – regular, display
// 6 – medium
// 7 – demi, demibold
// 8 – semi, semibold
// 9 – bold
// 10 – extra, extrabold
// 11 – heavy
// 12 – black
// 13 – ultrablack
// 14 – extrablack
if let font: NSFont = manager.fontWithFamily(name, traits: manager.traitsOfFont(font), weight: weight, size: font.pointSize) {
self.font = font
}
}
}
P.S. Bold weight works probably because it uses a slightly different logic – there's boldSystemFontOfSize(_:) that's available since OS X 10.0, unlike many other methods, which storyboard might rely upon.

QListWidget with custom widget does not scroll properly in Mac OS

In my application I'm having a QListWidget with custom widget items, containing among others a progress bar, set by QListWidget::setWidget(). I have been forced using a QListWidget instead of a QListView and a delegate, because the view/delegate solution does not animate the progress bar in the item widget as it would do normally.
My problem is that when scrolling the QListWidget, the custom widgets does not follow. They seem to be simply "glued" to the background. Only when resizing the surrounding widget manually, the QListWidget rearranges the child widgets correctly.
Here's how I'm adding a widget to the QListWidget:
QListWidgetItem* widgetItem = new QListWidgetItem("");
DownloadItemForm* dlItemForm = new DownloadItemForm(el);
widgetItem->setSizeHint(dlItemForm->size());
ui->listWidget->addItem(widgetItem);
ui->listWidget->setItemWidget(widgetItem, dlItemForm);
widgetItems.insert(el, widgetItem);
Am I missing something here? Seems like others are having problems as well (the last comment is mine):
https://bugreports.qt-project.org/browse/QTBUG-27043
Does anyone know of a workaround, or am I simply just missing something here?
Edit:
Just to clarify a bit further: I'm using Qt 4.8.4. While I hoped the issue would be fixed in 5.x, it's still there in 5.0.1
On Mac OS, a progress bar is animated: Even when there is no progress update, the progress bar has a slowly flowing animation within it. This animation is not running when using view based widget and an item delegate. Running the Torrent Qt example on a Mac will support this claim. The progress bar animation works when using QListWidget, injecting widget items containing a form widget with a progress bar.
Looking at the Qt 4.8.4 source, QListWidget::setItemWidget() looks like this:
void QListWidget::setItemWidget(QListWidgetItem *item, QWidget *widget)
{
Q_D(QListWidget);
QModelIndex index = d->listModel()->index(item);
QAbstractItemView::setIndexWidget(index, widget);
}
which tells me that the problem probably lies within QAbstractItemView. I suppose I'll have to jump into it and try to fix the problem from there, and hopefully create a patch, unless someone comes up with a revelation.
I've found a workaround, just connect your listWidget's scroll bar's valueChanged(int) signal to the listWidget's updateEditorGeometries() slot:
connect(_listWidget->verticalScrollBar(),
SIGNAL(valueChanged(int)),_listWidget,
SLOT(updateEditorGeometries()));
connect(_listWidget->horizontalScrollBar(),
SIGNAL(valueChanged(int)),_listWidget,
SLOT(updateEditorGeometries()));
As of 2013-08-05 git://gitorious.org/qt/qt.git revision 78b4162a352ddac398b8af7f8be33b009c333244 has the fix. Building from that revision or later fixes the issue.
This is the commit message for that revision:
Fix QListWidget item widget scroll bug
This fix applies the patch uploaded in the bug report. The patch was
provided by Qt-Commercial support but seems to have never found it's
way to the sources although the bug was fixed in Qt 5.
If you use homebrew you can simply:
brew install qt --build-from-source --HEAD
I have tested and confirmed this fixes the issue.
I applied this patch provided to me by Qt Commercial Support. This resolved my Mac scroll problems on Qt 4.8.5:
--- /Users/irfanomair/dev/qt-src-carbon-4.8.0/src/gui/kernel/qwidget_mac.mm 2011-12-15 10:38:21.000000000 -0800
+++ kernel/qwidget_mac.mm 2012-09-18 17:17:03.000000000 -0700
## -1,22 +1,41 ##
## -4684,15 +4703,14 ##
}
// Scroll the whole widget if qscrollRect is not valid:
- QRect validScrollRect = qscrollRect.isValid() ? qscrollRect : q->rect();
- validScrollRect &= clipRect();
+ QRect validScrollRect = qscrollRect.isValid() ? qscrollRect : QRect(0, 0, q->width(), q->height());
// If q is overlapped by other widgets, we cannot just blit pixels since
// this will move overlapping widgets as well. In case we just update:
const bool overlapped = isOverlapped(validScrollRect.translated(data.crect.topLeft()));
const bool accelerateScroll = accelEnv && isOpaque && !overlapped;
const bool isAlien = (q->internalWinId() == 0);
- const QPoint scrollDelta(dx, dy);
+
// If qscrollRect is valid, we are _not_ supposed to scroll q's children (as documented).
// But we do scroll children (and the whole of q) if qscrollRect is invalid. This case is
## -4714,7 +4732,6 ##
}else {
update_sys(qscrollRect);
}
- return;
}
#ifdef QT_MAC_USE_COCOA
## -4731,6 +4748,7 ##
// moved when the parent is scrolled. All directly or indirectly moved
// children will receive a move event before the function call returns.
QWidgetList movedChildren;
+ const QPoint scrollDelta(dx, dy);
if (scrollChildren) {
QObjectList children = q->children();

UICollectionView effective drag and drop

I am currently trying to implement the UITableView reordering behavior using UICollectionView.
Let's call a UItableView TV and a UICollectionView CV (to clarify the following explanation)
I am basically trying to reproduce the drag&drop of the TV, but I am not using the edit mode, the cell is ready to be moved as soon as the long press gesture is triggered. It works prefectly, I am using the move method of the CV, everything is fine.
I update the contentOffset property of the CV to handle the scroll when the user is dragging a cell. When a user goes to a particular rect at the top and the bottom, I update the contentOffset and the CV scroll. The problem is when the user stop moving it's finger, the gesture doesn't send any update which makes the scroll stop and start again as soon as the user moves his finger.
This behavior is definitely not natural, I would prefer continu to scroll until the user release the CV as it is the case in the TV. The TV drag&drop experience is awesome and I really want to reproduce the same feeling. Does anyone know how they manage the scroll in TV during reordering ?
I tried using a timer to trigger a scroll action repeatedly as long as the gesture position is in the right spot, the scroll was awful and not very productive (very slow and jumpy).
I also tried using GCD to listen the gesture position in another thread but the result is even worst.
I ran out of idea about that, so if someone has the answer I would marry him!
Here is the implementation of the longPress method:
- (void)handleLongPress:(UILongPressGestureRecognizer *)sender
{
ReorganizableCVCLayout *layout = (ReorganizableCVCLayout *)self.collectionView.collectionViewLayout;
CGPoint gesturePosition = [sender locationInView:self.collectionView];
NSIndexPath *selectedIndexPath = [self.collectionView indexPathForItemAtPoint:gesturePosition];
if (sender.state == UIGestureRecognizerStateBegan)
{
layout.selectedItem = selectedIndexPath;
layout.gesturePoint = gesturePosition; // Setting gesturePoint invalidate layout
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
layout.gesturePoint = gesturePosition; // Setting gesturePoint invalidate layout
[self swapCellAtPoint:gesturePosition];
[self manageScrollWithReferencePoint:gesturePosition];
}
else
{
[self.collectionView performBatchUpdates:^
{
layout.selectedItem = nil;
layout.gesturePoint = CGPointZero; // Setting gesturePoint invalidate layout
} completion:^(BOOL completion){[self.collectionView reloadData];}];
}
}
To make the CV scroll, I am using that method:
- (void)manageScrollWithReferencePoint:(CGPoint)gesturePoint
{
ReorganizableCVCLayout *layout = (ReorganizableCVCLayout *)self.collectionView.collectionViewLayout;
CGFloat topScrollLimit = self.collectionView.contentOffset.y+layout.itemSize.height/2+SCROLL_BORDER;
CGFloat bottomScrollLimit = self.collectionView.contentOffset.y+self.collectionView.frame.size.height-layout.itemSize.height/2-SCROLL_BORDER;
CGPoint contentOffset = self.collectionView.contentOffset;
if (gesturePoint.y < topScrollLimit && gesturePoint.y - layout.itemSize.height/2 - SCROLL_BORDER > 0)
contentOffset.y -= SCROLL_STEP;
else if (gesturePoint.y > bottomScrollLimit &&
gesturePoint.y + layout.itemSize.height/2 + SCROLL_BORDER < self.collectionView.contentSize.height)
contentOffset.y += SCROLL_STEP;
[self.collectionView setContentOffset:contentOffset];
}
This might help
https://github.com/lxcid/LXReorderableCollectionViewFlowLayout
This is extends the UICollectionView to allow each of the UICollectionViewCells to be rearranged manually by the user with a long touch (aka touch-and-hold). The user can drag the Cell to any other position in the collection and the other cells will reorder automatically. Thanks go to lxcid for this.
Here is an alternative:
The differences between DraggableCollectionView and LXReorderableCollectionViewFlowLayout are:
The data source is only changed once. This means that while the user is dragging an item the cells are re-positioned without modifying the data source.
It's written in such a way that makes it possible to use with custom layouts.
It uses a CADisplayLink for smooth scrolling and animation.
Animations are canceled less frequently while dragging. It feels more "natural".
The protocol extends UICollectionViewDataSource with methods similar to UITableViewDataSource.
It's a work in progress. Multiple sections are now supported.
To use it with a custom layout see DraggableCollectionViewFlowLayout. Most of the logic exists in LSCollectionViewLayoutHelper. There is also an example in CircleLayoutDemo showing how to make Apple's CircleLayout example from WWDC 2012 work.
As of iOS 9, UICollectionView now supports reordering.
For UICollectionViewControllers, just override collectionView(collectionView: UICollectionView, moveItemAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath)
For UICollectionViews, you'll have to handle the gestures yourself in addition to implementing the UICollectionViewDataSource method above.
Here's the code from the source:
private var longPressGesture: UILongPressGestureRecognizer!
override func viewDidLoad() {
super.viewDidLoad()
longPressGesture = UILongPressGestureRecognizer(target: self, action: "handleLongGesture:")
self.collectionView.addGestureRecognizer(longPressGesture)
}
func handleLongGesture(gesture: UILongPressGestureRecognizer) {
switch(gesture.state) {
case UIGestureRecognizerState.Began:
guard let selectedIndexPath = self.collectionView.indexPathForItemAtPoint(gesture.locationInView(self.collectionView)) else {
break
}
collectionView.beginInteractiveMovementForItemAtIndexPath(selectedIndexPath)
case UIGestureRecognizerState.Changed:
collectionView.updateInteractiveMovementTargetPosition(gesture.locationInView(gesture.view!))
case UIGestureRecognizerState.Ended:
collectionView.endInteractiveMovement()
default:
collectionView.cancelInteractiveMovement()
}
}
Sources:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UICollectionView_class/#//apple_ref/doc/uid/TP40012177-CH1-SW67
http://nshint.io/blog/2015/07/16/uicollectionviews-now-have-easy-reordering/
If you want to experiment rolling out your own, I just wrote a Swift based tutorial you can look. I tried to build the most basic of cases so as to be easier to follow this.
Here is another approach:
Key difference is that this solution does not require a "ghost" or "dummy" cell to provide the drag and drop functionality. It simply uses the cell itself. Animations are in line with UITableView. It works by adjusting the collection view layout's private datasource while moving around. Once you let go, it will tell your controller that you can commit the change to your own datasource.
I believe it's a bit simpler to work with for most use cases. Still a work in progress, but yet another way to accomplish this. Most should find this pretty easy to incorporate into their own custom UICollectionViewLayouts.

Resources