I am attempting to add a gradient to a collection view cell as seen below:
So far the result I get does not cover the entire cell:
I have a
collection view cell:
class CollectionViewCell: UICollectionViewCell {
#IBOutlet weak var cellView: UIView!
let diagonalGradient = DiagonalGradient()
override func layoutSubviews() {
super.layoutSubviews()
diagonalGradient.frame = cellView.bounds
}
as well as a Custom Gradient Class:
class DiagonalGradient: UIView {
let gradient = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame:frame)
setupGradient(color: UIColor.red))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupGradient(color: UIColor.red))
}
func setupGradient(color: UIColor ) {
gradient.colors = [
UIColor.clear.cgColor,
color.cgColor
]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
gradient.frame = bounds
layer.addSublayer(gradient)
}
}
Lastly, in the story board I assign the class of the cellView to my custom gradient class. How do I fix this?
You're setting the gradient layer frame at the wrong time.
Move it to layoutSubviews():
class DiagonalGradient: UIView {
let gradient = CAGradientLayer()
override init(frame: CGRect) {
super.init(frame:frame)
setupGradient(color: UIColor.red))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupGradient(color: UIColor.red))
}
func setupGradient(color: UIColor ) {
gradient.colors = [
UIColor.clear.cgColor,
color.cgColor
]
gradient.startPoint = CGPoint(x: 0, y: 1)
gradient.endPoint = CGPoint(x: 1, y: 0)
// don't do this here
//gradient.frame = bounds
layer.addSublayer(gradient)
}
override func layoutSubviews() {
super.layoutSubviews()
// do this here
gradient.frame = bounds
}
}
I tried to manually reproduce python dict underlying code(it can only implement setitem and getitem), but I met a problem: The hashmap I wrote can only work well when the value type is basic data types(like int,str etc).If the value's type is object, the hashmap can only set item, the python will crash when getting the object type's value.
The error message is "
Fatal error: GC object already tracked"
I guess there are several possible problems:
something wrong in the (PyTypeObject)PyHashMap_Type definition
The getitem method return's PyObject*, but the python cannot resolve the object's pointer
Here are some cases I have test
case1:the key and value types are all basic data types, it works well
case2:the key's type is object,but the value type is basic data type ,
it works well
case3:the key'type is basic data type, but the value type is object,
the python crash when getting item, even though the value is the an
empty object
case4:the key and value types are all object, the result is the same
as case3
// the PyTypeObject
static PyTypeObject PyHashMap_Type = {
PyVarObject_HEAD_INIT(NULL, 0)
"hashmap",
sizeof(PyMapObject),
0,
(destructor)PyHashMap_dealloc, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
(reprfunc)repr_func, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
PyObject_GenericGetAttr, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
Py_TPFLAGS_BASETYPE | Py_TPFLAGS_DICT_SUBCLASS, /* tp_flags */
0, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
PyType_GenericAlloc, /* tp_alloc */
_HashMap_New, /* tp_new */
PyObject_GC_Del, /* tp_free */
};
// the hashmap struct
typedef struct _mapkeysobject PyMapKeysObject;
typedef struct {
Py_hash_t me_hash;
PyObject *me_key;
PyObject *me_value;
} PyMapKeyEntry;
struct _mapkeysobject {
Py_ssize_t dk_size;
Py_ssize_t dk_usable;
PyMapKeyEntry dk_entries[1];
};
typedef struct {
PyObject_HEAD
Py_ssize_t ma_used;
PyMapKeysObject *ma_keys;
PyObject **ma_values;
} PyMapObject;
// get item methods, the python call GET_ITEM_WRAPPER
static PyObject* GET_ITEM_WRAPPER(PyObject* self, PyObject* args){
PyObject * o = NULL;
PyObject * key = NULL;
if (!PyArg_ParseTuple(args, "OO",&o,&key)) {
printf("error: arg list error");
Py_RETURN_NONE;
}
PyObject* value = PyMap_GetItem(o, key);
if (value == NULL) Py_RETURN_NONE;
return value;
}
static PyObject* PyMap_GetItem(PyObject* o, PyObject* key){
PyMapObject *mp;
Py_hash_t hash;
mp = (PyMapObject *)o;
hash = PyObject_Hash(key);
if (hash == -1)
return NULL;
return searchmap(mp, key, hash);
}
static PyObject* searchmap(PyMapObject* mp, PyObject* key, Py_hash_t hash){
PyObject **value_addr;
PyMapKeyEntry *ep;
ep = lookup_function(mp, key, hash, &value_addr);
if (ep == NULL) return NULL;
return ep->me_value;
}
PyMapKeyEntry* lookup_function(PyMapObject* mp, PyObject* key, Py_hash_t hash, PyObject ***value_addr){
size_t i;
size_t perturb;
size_t mask = DK_MASK(mp->ma_keys);
PyMapKeyEntry *ep0 = &mp->ma_keys->dk_entries[0];
PyMapKeyEntry *ep;
i = (size_t)hash & mask;
ep = &ep0[i];
if (ep->me_key == NULL || ep->me_key == key) {
*value_addr = &ep->me_value;
return ep;
}
for (perturb = hash; ; perturb >>= PERTURB_SHIFT) {
i = (i << 2) + i + perturb + 1;
ep = &ep0[i & mask];
if (ep->me_key == NULL || ep->me_key == key) {
*value_addr = &ep->me_value;
return ep;
}
}
assert(0); /* NOT REACHED */
return 0;
}
I expect the value type can be object
I believe you've messed up the reference counting (although it's hard to be absolutely certain with incomplete code).
Your reference count for the stored values should be at least one (to represent the fact that you hold a reference to it as part of your dictionary). However, at the point you return it then it should be two (one for the reference that you hold, and one for the reference to the return value).
I suspect in PyMap_GetItem you should do:
PyObject *ret_val = searchmap(mp, key, hash);
Py_XINCREF(ret_val);
return ret_val;
(although I you don't seem to actually set the sequence/mapping methods in PyHashMap_Type so who knows if PyMap_GetItem is actually used...)
The reason this will appear to work for ints and string is that Python "interns" small ints and short strings, essentially keeping a constant reference to them. If you kept looking them up then it'd break in the end though (each time you do it you lose a reference).
I have MyType registered as qmlRegisterSingletonType<MyType>("org.examples", 1, 0, "MyType") which has QmlListProperty<MyListElement> - myList.
What syntax i should use at QML side to assign something to MyType.myList?
Seems like that is incorrect:
Component.onCompleted: {
MyType.myList = [
MyListElement {
}
]
}
You have to create the MyListElement object using javascript and then add it to the list with push since QmlListProperty is translated to a list in QML.
In this case with Qt.createQmlObject(), so assuming MyListElement was created with:
qmlRegisterType<MyListElement>("org.examples", 1, 0, "MyListElement");
So the solution is:
Component.onCompleted: {
var o = Qt.createQmlObject('import org.examples 1.0; MyListElement {}', this)
// o.foo_property = foo_value;
MyType.myList.push(o);
}
I am trying to implement a NSTableView that looks similar to the Xcode IB object selector (bottom right panel). As shown below when a row is selected a full width horizontal line is draw above and below the selected row.
I have successfully created a subclass of NSTableRowView and have used the isNextRowSelected property to determine whether to draw a full width separator and this almost works.
The issue is the row above the selected row is not being redrawn unless you happened to select a row and then select the row below it immediately afterwards.
How can I efficiently get the NSTableView to redraw the row above the selected row every time ?
Here is my implementation when a single row is selected
And another if a the row immediately below is now selected - which is what I want.
/// This subclass draws a partial line as the separator for unselected rows and a full width line above and below for selected rows
/// | ROW |
/// | ---------- | unselected separator
/// |------------| selected separator on row above selected row
/// | ROW |
/// |------------| selected separator
///
/// Issue: Row above selected row does not get redrawn when selected row is deselected
class OSTableRowView: NSTableRowView {
let separatorColor = NSColor(calibratedWhite: 0.35, alpha: 1)
let selectedSeparatorColor = NSColor(calibratedWhite: 0.15, alpha: 1)
let selectedFillColor = NSColor(calibratedWhite: 0.82, alpha: 1)
override func drawSeparator(in dirtyRect: NSRect) {
let yBottom = self.bounds.height
let gap: CGFloat = 4.0
let xLeft: CGFloat = 0.0
let xRight = xLeft + self.bounds.width
let lines = NSBezierPath()
/// Draw a full width separator if the item is selected or if the next row is selected
if self.isSelected || self.isNextRowSelected {
selectedSeparatorColor.setStroke()
lines.move(to: NSPoint(x: xLeft, y: yBottom))
lines.line(to: NSPoint(x: xRight, y: yBottom))
lines.lineWidth = 1.0
} else {
separatorColor.setStroke()
lines.move(to: NSPoint(x: xLeft+gap, y: yBottom))
lines.line(to: NSPoint(x: xRight-gap, y: yBottom))
lines.lineWidth = 0.0
}
lines.stroke()
}
override func drawSelection(in dirtyRect: NSRect) {
if self.selectionHighlightStyle != .none {
let selectionRect = self.bounds
selectedSeparatorColor.setStroke()
selectedFillColor.setFill()
selectionRect.fill()
}
}
}
After reading a few other posts I tried adding code to cause the preceding row to be redraw. This appears to have not effect.
func selectionShouldChange(in tableView: NSTableView) -> Bool {
let selection = tableView.selectedRow
if selection > 0 {
tableView.setNeedsDisplay(tableView.rect(ofRow: selection-1))
tableView.displayIfNeeded()
}
return true
}
And nor does this.
func tableViewSelectionDidChange(_ notification: Notification) {
guard let tableView = self.sidebarOutlineView else {
return
}
let row = tableView.selectedRow
if row > 0 {
tableView.setNeedsDisplay(tableView.rect(ofRow: row-1))
print("row-1 update rect: \(tableView.rect(ofRow: row-1))")
}
}
Seems odd that neither of these trigger redrawing of the row - am I missing something here!
EDIT:
OK I found something that seems to work OKish - there is still a visible lag in the redrawing of the row above the deselected row which is not present in the XCode tableView.
var lastSelectedRow = -1 {
didSet {
guard let tableView = self.sidebarOutlineView else {
return
}
if oldValue != lastSelectedRow {
if oldValue > 0 {
if let view = tableView.rowView(atRow: oldValue-1, makeIfNecessary: false) {
view.needsDisplay = true
}
}
if lastSelectedRow > 0 {
if let view = tableView.rowView(atRow: lastSelectedRow-1, makeIfNecessary: false) {
view.needsDisplay = true
}
}
}
}
}
and then simply set the value of the variable lastSelectedRow = tableView.selectedRow in the tableViewSelectionDidChange(:) method.
I think perhaps the tableView needs to be subclassed to make sure that both rows are redrawn in the same update cycle.
This NSTableRowView subclass seems to work fine with no visible lag in redrawing the row above any more.
The solution was to override the isSelected properly and set needsDisplay on the row above each time.
/// This subclass draws a partial line as the separator for unselected rows and a full width line above and below for selected rows
/// | ROW |
/// | ---------- | unselected separator
/// |------------| selected separator on row above selected row
/// | ROW |
/// |------------| selected separator
///
/// Issue: Row above selected row does not get redrawn when selected row is deselected
class OSTableRowView: NSTableRowView {
let separatorColor = NSColor(calibratedWhite: 0.35, alpha: 1)
let selectedSeparatorColor = NSColor(calibratedWhite: 0.15, alpha: 1)
let selectedFillColor = NSColor(calibratedWhite: 0.82, alpha: 1)
/// Override this and whenever it is changed set the previous row to be updated
override var isSelected: Bool {
didSet {
if let tableView = self.superview as? NSTableView {
let row = tableView.row(for: self)
if row > 0 {
tableView.rowView(atRow: row-1, makeIfNecessary: false)?.needsDisplay = true
}
}
}
}
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
override func drawSeparator(in dirtyRect: NSRect) {
let yBottom = self.bounds.height
let gap: CGFloat = 4.0
let xLeft: CGFloat = 0.0
let xRight = xLeft + self.bounds.width
let lines = NSBezierPath()
/// Draw a full width separator if the item is selected or if the next row is selected
if self.isSelected || self.isNextRowSelected {
selectedSeparatorColor.setStroke()
lines.move(to: NSPoint(x: xLeft, y: yBottom))
lines.line(to: NSPoint(x: xRight, y: yBottom))
lines.lineWidth = 1.0
} else {
separatorColor.setStroke()
lines.move(to: NSPoint(x: xLeft+gap, y: yBottom))
lines.line(to: NSPoint(x: xRight-gap, y: yBottom))
lines.lineWidth = 0.0
}
lines.stroke()
}
override func drawSelection(in dirtyRect: NSRect) {
if self.selectionHighlightStyle != .none {
let selectionRect = self.bounds
selectedSeparatorColor.setStroke()
selectedFillColor.setFill()
selectionRect.fill()
}
}
}
I am simply trying to create a view with a contained view that is 50% as tall and high. In the following code, I set constraints to achieve that when I add the view to the super-view. I set translatesAutoResizingMaskToConstraints to false, add the constraints via anchors. I also tried to call setNeedsUpdateConstraints, and add the same constraints in updateConstraints.
But in the following Playground code, I don't see the constrained subview testView. I expect to see an blue view half the size of the orange view, but all i see is orange.
I'm not sure what I am missing here.
//: Playground - noun: a place where people can play
import UIKit
import PlaygroundSupport
class LView: UIView {
var testView = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .orange
setup()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setup() {
addSubview(testView)
testView.backgroundColor = .blue
testView.text = "....."
//testView.setNeedsUpdateConstraints()
testView.translatesAutoresizingMaskIntoConstraints = false
testView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5).isActive = true
testView.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 0.5).isActive = true
testView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
testView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
}
}
let testView = LView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
PlaygroundPage.current.liveView = testView