Skip to content

Commit

Permalink
feat(MD-3347): add drage select for ios (#90)
Browse files Browse the repository at this point in the history
* feat(MD-3347): add drage select for ios

* fix: rubber band the selection box

* chore: fix lint

* fix: resize only when width > 0

* fix: selection interaction

* fix: use contain

* Update ios/SelectionBand.swift

Co-authored-by: Luc Gruska <luc.gruska@qlik.com>

* fix: resolve comments

Co-authored-by: Vittorio Cellucci <vel@qlik.com>
Co-authored-by: Luc Gruska <luc.gruska@qlik.com>
  • Loading branch information
3 people authored and enell committed Feb 9, 2023
1 parent 69342d5 commit f12f090
Show file tree
Hide file tree
Showing 14 changed files with 499 additions and 113 deletions.
150 changes: 75 additions & 75 deletions ios/ContainerView.swift

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions ios/DataCellView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ class DataCellView: UICollectionViewCell {
var cellColor: UIColor?
var numberOfLines = 1
var isDataView = true
weak var selectionBand: SelectionBand?
weak var dataCollectionView: DataCollectionView?

static let iconMap: [String: UniChar] = ["m": 0xe96c,
"è": 0xe997,
"ï": 0xe951,
Expand Down Expand Up @@ -117,6 +120,8 @@ class DataCellView: UICollectionViewCell {
label.column = index
label.cell = element
label.checkSelected(selectionsEngine)
label.selectionBand = self.selectionBand
label.dataCollectionView = self.dataCollectionView

label.checkForUrls()
if representation.type == "indicator", let indicator = element.indicator, let uniChar = DataCellView.iconMap[indicator.icon ?? "m"] {
Expand Down
28 changes: 28 additions & 0 deletions ios/DataCollectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ class DataCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDe
var dataRange: CountableRange = 0..<2
var freezeFirstColumn = false
var grabbers: [() -> GrabberView?]?
private var lasso = false
weak var totalCellsView: TotalCellsView?
weak var columnWidths: ColumnWidths?
weak var slave: DataCollectionView?
weak var headerView: HeaderView?
weak var totalsView: TotalsView?
weak var hScrollView: UIScrollView?
weak var selectionBand: SelectionBand?

init(frame: CGRect, withRows rows: [DataRow],
andColumns cols: [DataColumn],
Expand All @@ -49,11 +51,14 @@ class DataCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDe
let colorParser = ColorParser()
self.cellStyle = cellStyle
self.dataRange = range
self.clipsToBounds = false

if let colorString = cellStyle.color {
cellColor = colorParser.fromCSS(cssString: colorString)
}
setData(columns: cols, withRows: rows)
fitToFrame()
createSelectionBands()
}

fileprivate func fitToFrame() {
Expand All @@ -69,6 +74,16 @@ class DataCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDe
self.addConstraints([top, bottom, left, right])
}

fileprivate func createSelectionBands() {
guard let childCollectionView = childCollectionView else { return }

let selectionBand = SelectionBand(frame: self.frame)
selectionBand.parentCollectionView = self
childCollectionView.addSubview(selectionBand)
self.selectionBand = selectionBand

}

required init?(coder: NSCoder) {
super.init(coder: coder)
}
Expand Down Expand Up @@ -190,6 +205,8 @@ class DataCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDe
// swiftlint:disable:next force_cast
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! DataCellView
cell.isDataView = self.isDataView
cell.selectionBand = self.selectionBand
cell.dataCollectionView = self
cell.backgroundColor = isDataView ? indexPath.row % 2 == 0 ? .white : UIColor(red: 0.97, green: 0.97, blue: 0.97, alpha: 1.0) : .white
cell.cellColor = cellColor
cell.numberOfLines = cellStyle.rowHeight ?? 1
Expand Down Expand Up @@ -353,4 +370,15 @@ class DataCollectionView: UIView, UICollectionViewDataSource, UICollectionViewDe
startX += width
})
}

func setLasso(_ lasso: Bool) {
self.lasso = lasso
if let childCollectionView = childCollectionView {
childCollectionView.isScrollEnabled = !self.lasso
}
}

func getOffset() -> CGFloat {
return HorizontalScrollValues.RightScrollContentPadding.rawValue - HorizontalScrollValues.HorizontalPadding.rawValue
}
}
27 changes: 27 additions & 0 deletions ios/DragBox.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// DragBox.swift
// react-native-simple-grid
//
// Created by Vittorio Cellucci on 2022-10-13.
//

import Foundation

class DragBox: UIView {

override func draw(_ rect: CGRect) {

guard let context = UIGraphicsGetCurrentContext() else {return}

context.setFillColor(self.backgroundColor?.cgColor ?? UIColor.black.cgColor)
context.fill(rect)
// draw two thin grabbers
// center rect
let width = rect.width * 0.5
let x = (rect.width - width) / 2.0
let g = CGRect(origin: CGPoint(x: x, y: rect.height - 4 ), size: CGSize(width: width, height: 1))
context.setFillColor(UIColor.white.withAlphaComponent(0.5).cgColor)
context.fill(g)
context.fill(g.offsetBy(dx: 0, dy: -2))
}
}
7 changes: 7 additions & 0 deletions ios/Extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,12 @@ extension UIView {
height: bounds.height)).cgPath

}
}

extension Notification.Name {
static let onTappedSelection = Notification.Name("onTappedSelection")
static let onTappedSelectionBand = Notification.Name("onTappedSelectionBand")
static let onClearSelectionBand = Notification.Name("onClearSelectionBand")
static let onSelectionDragged = Notification.Name("onSelectionDragged")
static let onDragSelectDone = Notification.Name("onDragSelectDone")
}
12 changes: 12 additions & 0 deletions ios/GrabberView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ class GrabberView: UIView {
weak var totalsView: TotalsView?
weak var scrollView: UIScrollView?
weak var guideLineView: GuideLineView?
weak var primarySelectionBand: SelectionBand?
weak var secondarySelectionBand: SelectionBand?

var pressed = false
var trim: CGFloat = 0
var timer: Timer?
Expand Down Expand Up @@ -128,6 +131,14 @@ class GrabberView: UIView {
if let totalsView = totalsView {
totalsView.updateSize(translation, withColumn: abosluteColIdx)
}

if let primarySelectionBand = self.primarySelectionBand {
primarySelectionBand.updateSize(translation, withColumn: colIdx)
}

if let secondarySelectionBand = self.secondarySelectionBand {
secondarySelectionBand.updateSize(translation, withColumn: colIdx)
}
return true
}

Expand Down Expand Up @@ -172,6 +183,7 @@ class GrabberView: UIView {
if let container = containerView {
container.onEndDragged(colIdx)
}

}

func repositionTo(_ x: Double) {
Expand Down
4 changes: 4 additions & 0 deletions ios/MasterColumnCollectionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,8 @@ class MasterColumnCollectionView: DataCollectionView {
view.frame = CGRect(origin: view.frame.origin, size: CGSize(width: width, height: view.frame.height))
}

override func getOffset() -> CGFloat {
return 0.0
}

}
93 changes: 82 additions & 11 deletions ios/PaddedLabel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@ class PaddedLabel: UILabel, SelectionsListener {
var selected = false
var selectionsEngine: SelectionsEngine?
var url: URL?
weak var selectionBand: SelectionBand?
weak var dataCollectionView: DataCollectionView?

private static let numberFormatter = NumberFormatter()

override var intrinsicContentSize: CGSize {
var s = super.intrinsicContentSize
s.height += UIEI.top + UIEI.bottom
s.width += UIEI.left + UIEI.right
return s
}

override init(frame: CGRect) {
super.init(frame: frame.integral)
}
Expand Down Expand Up @@ -63,21 +58,78 @@ class PaddedLabel: UILabel, SelectionsListener {

addGestureRecognizer(tapGesture)
selectionsEngine.addListener(listener: self)
NotificationCenter.default.addObserver(self, selector: #selector(onTappedSelectionBand), name: Notification.Name.onTappedSelectionBand, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(onSelectionDragged), name: Notification.Name.onSelectionDragged, object: nil)
}

@objc func labelClicked(_ sender: UITapGestureRecognizer) {
if sender.state == .ended {
if let url = url {
UIApplication.shared.open(url)
} else {
let sig = SelectionsEngine.buildSelectionSignator(from: cell!)
if let selectionsEngine = selectionsEngine {
selectionsEngine.toggleSelected(sig)
guard let selectionsEngine = self.selectionsEngine else {return}
if selectionsEngine.canSelect(self.cell!) {
if let selectionBand = self.selectionBand {
if !selected {
let convertedFrame = convertLocalFrameToSelectionBandFrame(selectionBand)
let envelope = SelectionBandEnvelope(convertedFrame, sender: self, colIdx: self.cell?.colIdx ?? -1.0)
selectionBand.handleActivation(envelope)
NotificationCenter.default.post(name: Notification.Name.onTappedSelection, object: envelope)
}
}
toggleSelection()
}
}
}
}

@objc func onTappedSelectionBand(notificaiton: Notification) {
guard let point = notificaiton.object as? CGPoint else {return}
guard let parentView = self.selectionBand else {return}
let hitTestPoint = parentView.convert(point, to: self)
if self.frame.contains(hitTestPoint) {
toggleSelection()
if !selected {
NotificationCenter.default.post(name: Notification.Name.onClearSelectionBand, object: nil)
}
}
}

@objc func onSelectionDragged(notificaiton: Notification) {
if !selected {
guard let envelope = notificaiton.object as? SelectionBandEnvelope else {return}
guard let selectionBand = self.selectionBand else {return}
if envelope.sender === selectionBand {
let convertedFrame = convertLocalFrameToSelectionBandFrame(selectionBand)
if envelope.frame.contains(convertedFrame) {
addToSelections()
}
}
}
}

fileprivate func convertLocalFrameToSelectionBandFrame(_ selectionBand: UIView) -> CGRect {
let convertedFrame = convert(self.frame, from: self.superview)// convert(self.frame, to: dataCollectionView) //dataCollectionView.convert(self.frame, to: selectionBand)
// account for the 1 width colunm grabber line
return convert(convertedFrame, to: selectionBand).insetBy(dx: 0.5, dy: 0).offsetBy(dx: -0.5, dy: 0)// .offsetBy(dx: -dataCollectionView.frame.origin.x, dy: 0)
}

fileprivate func addToSelections() {
guard let selectionsEngine = self.selectionsEngine else { return }
let sig = SelectionsEngine.buildSelectionSignator(from: cell!)
selectionsEngine.addSelection(sig)
selected = true
updateBackground()

}

fileprivate func toggleSelection() {
let sig = SelectionsEngine.buildSelectionSignator(from: cell!)
if let selectionsEngine = selectionsEngine {
selectionsEngine.toggleSelected(sig)
}
}

func clearSelected() {
selected = false
updateBackground()
Expand All @@ -92,14 +144,29 @@ class PaddedLabel: UILabel, SelectionsListener {
}
}

func addedToSelection(data: String) {
let sig = SelectionsEngine.signatureKey(from: data)
let comp = SelectionsEngine.signatureKey(from: cell!)
if sig == comp {
selected = true
updateBackground()
}
}

func checkSelected(_ selectionsEngine: SelectionsEngine) {
selected = selectionsEngine.contains(cell!)
updateBackground()
}

fileprivate func updateBackground() {
backgroundColor = selected ? selectedBackgroundColor : .clear
textColor = selected ? .white : .black
animateBackgroundColor(to: selected ? selectedBackgroundColor : .clear)
}

fileprivate func animateBackgroundColor(to: UIColor) {
UIView.animate(withDuration: 0.3, animations: {
self.layer.backgroundColor = to.cgColor
})
}

func addSystemImage(imageName: String, afterLabel bolAfterLabel: Bool = false) {
Expand Down Expand Up @@ -197,4 +264,8 @@ class PaddedLabel: UILabel, SelectionsListener {
self.attributedText = attributedString1
}
}

deinit {
NotificationCenter.default.removeObserver(self.onTappedSelectionBand)
}
}
3 changes: 1 addition & 2 deletions ios/ReactNativeStraightTableViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ @interface RCT_EXTERN_MODULE(ReactNativeStraightTableViewManager, RCTViewManager
RCT_EXPORT_VIEW_PROPERTY(onEndReached, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onSelectionsChanged, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onHeaderPressed, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(onDoubleTap, RCTDirectEventBlock)
RCT_EXPORT_VIEW_PROPERTY(freezeFirstColumn, BOOL)
RCT_EXPORT_VIEW_PROPERTY(cellContentStyle, NSDictionary*)
RCT_EXPORT_VIEW_PROPERTY(headerContentStyle, NSDictionary*)
RCT_EXPORT_VIEW_PROPERTY(isDataView, BOOL)
RCT_EXPORT_VIEW_PROPERTY(isPercent, BOOL)
RCT_EXPORT_VIEW_PROPERTY(name, NSString*)
RCT_EXPORT_VIEW_PROPERTY(onConfirmSelections, RCTDirectEventBlock)
@end
Loading

0 comments on commit f12f090

Please sign in to comment.