GridKit is a flexible and customizable grid-based UI framework for iOS, designed to help developers create dynamic layouts with ease. It provides a structured way to define grids, manage layouts, and handle interactions.
-
Open Xcode > File > Add Package Dependency
-
Enter:
https://github.com/Devansh-Seth-DEV/GridKit.git
- Select branch or version 1.0.0 and click Add Package.
If you are manually downloading from Releases, extract GridKit.xcframework.zip
and add it to your project.
A Subclass of UIView
representing a cell in the canvas
init?(coder: NSCoder)
init(
frame: CGRect,
row: Int,
column: Int
)
init(
row: Int,
column: Int,
frame: CGRect,
cellColor: UIColor?,
borderWidth: CGFloat?,
borderColor: UIColor?,
cornerRadius: CGFloat?
)
-
var canAcceptDetach: Bool
- Checks whether this cell can accepts more detaches of cell or not -
var canAcceptDrop: Bool
- Check whether this cell can accept more drops or not -
var column: Int
- Column at which cell is present in canvas -
var dragable: Bool
- Flag which allow a cell to be able to be dragged in canvas, bydefault set to false
-
var dropStorageCount: Int
- Stores the maxmium number of cells currently droped on it -
var dropable: Bool
- Flag which allow a cell to take another cell onto it or in other words to allow another cell to be droped into it, bydefault set to false
-
var initialOrigin: CGPoint
- Initial origin of cell in the canvas, updated as cell’s position changes -
var maxDropCapacity: Int
- Stores the maximum capacity to accepts the drops on this view,default is 1
-
var row: Int
- Row at which cell is present in canvas
A structure that defines the specifications of the canvas, including the maximum number of rows and columns required to make the canvas and the size taken by single cell in the canvas
init(
rows: Int,
columns: Int,
cellSize: CGFloat,
interCellInsets: CGFloat,
cellColor: UIColor?,
canvasColor: UIColor?,
cellBorderWidth: CGFloat?,
cellBorderColor: UIColor?,
cellCornerRadius: CGFloat?,
canvasCornerRadius: CGFloat?
)
-
var canvasColor: UIColor?
- Defines the background color of the canvas,default is set to white
-
var canvasCornerRadius: CGFloat?
- Defines the corner radius of canvas,deafult is set to nil
-
var canvasHeight: CGFloat
- Computes the total grid height based on the cell size and spacing between the cells.` -
var canvasWidth: CGFloat
- Computes the total grid width based on the cell size and spacing between the cells.` -
var cellBorderColor: UIColor?
- Defines the border color of the cell in canvas,default is set to black
-
var cellBorderWidth: CGFloat?
- Defines the border width of the cell in canvas,default is set to nil
-
var cellColor: UIColor?
- Defines the backgound color of the cell,default is set to tertiarySystemGroupedBackground color
-
var cellCornerRadius: CGFloat?
- Defines the corner radius of a cell in canvas,default is set to nil
-
var cellSize: CGFloat
- Defines the size of a single cell in a canvas -
var columns: Int
- Defines Maximum number of columns in a canvas -
var interCellInsets: CGFloat
- Defines the spacing between two cells in a canvas either horizontally or vertically,default is set to 0
-
var rows: Int
- Defines Maximun number of rows in a canvas
It is a manager to and handles cell interaction in GKLayoutView. Each cell is assigned a unique tag
i.e index of the cell in the canvas depending upon the row and column in which cell in placed
// Initializes the Canvas with a given canvas specifications.
init(spec: GKSpec)
-
var gkCells: [IndexPath : GKCell]
- Track all the cells to be displayed on the canvas with their positions -
var gklayout: UIView?
- Superview which display the cells -
var gkspec: GKSpec
- Specifications of the canvas
Add a marker at the corner of a cell in the canvas at given position, marker will be added even if cell not exists
public func addCornerMarker(
at indexPath: IndexPath,
size: CGSize = CGSize(width: 4, height: 4),
offset: (dx: CGFloat, dy: CGFloat) = (dx: 2, dy: 2),
cornerRadius: CGFloat = 2,
backgroundColor: UIColor? = nil,
borderWidth: CGFloat = 0,
borderColor: UIColor? = .clear
)
Add markers at the corner of the cell in the canvas which satisfy certain condition on row and column, marker will be added even if cell not exists
func addCornerMarkers(where: ((_ row: Int, _ col: Int) -> Bool))
Attaches the layout view after initialization
func attachLayoutView(GKLayoutView)
Calculates the frame of a cell based on its position in the canvas
func computeCellFrame(
row: Int,
column: Int
) -> CGRect
Computes the cell origin based on its position in the canvas
func computeCellOrigin(
row: Int,
column: Int
) -> CGPoint
Fill the canvas with markers
func displayMarkers()
Erase all cells from the canvas.
func erase()
Returns all the cells present in the canvas
func getAllCells() -> [GKCell]
Returns all the positions of the cell in the canvas
func getAllIndexPaths() -> [IndexPath]
Returns the cell at given index if exists
func getCell(at: IndexPath?) -> GKCell?
Returns cell at a point if exists
func getCell(atPoint: CGPoint) -> GKCell?
Returns the cell from the canvas at given row and column if exists
func getCell(
atRow: Int,
column: Int
) -> GKCell?
Returns the size of single cell
func getCellSize() -> CGSize
Returns the index of cell at a point if exists
func getIndexPath(forCellAt: CGPoint) -> IndexPath?
Returns the index path of a cell in canvas if exists
func getIndexPath(ofCell: GKCell) -> IndexPath
Returns the size of canvas
func getSize() -> CGSize
Checks if a cell exists at a given position.
func hasCell(
atRow: Int,
column: Int
) -> Bool
Inserts a cell at a specific position
func insertCell(
atRow: Int,
column: Int
)
Refreshes the canvas layout
func layoutCanvasCells()
Fill the canvas with cells
func populate()
Fill the canvas with cells at given positions
func populateCells(at: [(row: Int, col: Int)])
Fill the canvas with cells which satisfy certain conditions on row and column
func populateCells(where: ((Int, Int) -> Bool))
Removes a cell from a specific position
func removeCell(
atRow: Int,
column: Int
)
Remove cells from given position
func removeCells(at: [(row: Int, col: Int)])
Remove cells which satisfy certain condition on row and column
func removeCells(where: ((Int, Int) -> Bool))
Removes a marker from the corner of the cell in the canvas at given position, marker will be added even if cell not exists
func removeCornerMarker(from: IndexPath)
Removes markers from the corner of the cells in the canvas which satisfy certain conditions on row and column, marker will be added even if cell not exists
func removeCornerMarker(where: ((_ row: Int, _ col: Int) -> Bool))
Removes all the markers from the canvas
func removeMarkers()
Updates cell properties at a given position
func updateCell(
atRow: Int,
column: Int,
update: ((inout GKCell) -> Void)?
)
Updates the cell position in canvas to new row or column
func updateCellPosition(
withIndexPath: IndexPath,
to: IndexPath
)
Update cells properties at given pair of positions (row, col)
func updateCells(
at: [(row: Int, col: Int)],
update: ((inout GKCell) -> Void)?
)
Update cells properties which satisfy certain condition on row and column
func updateCells(
where: ((Int, Int) -> Bool),
update: ((inout GKCell) -> Void)?
)
Update cells properties at given positions
func updateCells(
with: [IndexPath],
update: ((inout GKCell) -> Void)?
)
A UIView subclass to display the cells in grid(2d-matrix) fashion, it uses GKCanvas to manage the cells
init?(coder: NSCoder)
init(
frame: CGRect,
spec: GKSpec,
in: UIView,
masksToBounds: Bool
)
init(
spec: GKSpec,
in: UIView,
masksToBounds: Bool
)
-
var allowCellDragNDrop: Bool
- Flag which allow the cells in the canvas to be dragged in the canvas and dropped onto another cell -
var allowDropInEmptySpace: Bool
- Flag which allow the cells in the canvas to be dropped in empty space -
var allowPathDrawing: Bool
- Flag which allow canvas to make path between two cells when finger slides from one cell to another, tapping on the cell will not work after its activation instead you can update cell properties inside updateCellOnTrace or updateCellOnTraceEnd -
var canvas: GKCanvas
- Canvas which manages the cells in the layout -
var columns: Int
- Computes the number of row in the canvas -
var didTapCell: ((_ cell: GKCell) -> Void)?
- Function to call when a cell gets tapped, allowPathDrawing should be set to false for its working Provide cell which gets tapped -
var initialDragOrigin: CGPoint
- Stores origin of cell when it begans to drag -
var onCellDetach: ((GKCell, GKCell) -> Void)?
- Function to call before detaching the cell from another cell Provides two parameters dragingCell and acceptorCell -
var onCellDropped: ((GKCell, GKCell) -> Void)?
- Function to call before droping the cell into another cell Provides two parameters dragingCell and acceptorCell -
var pathLayers: [CAShapeLayer]
- Track all the lines with the order in which they connect two cells, allowPathDrawing should be set to true for its working -
var pathLineWidth: CGFloat
- Stores the width of the line connecting two cells -
var pathStrokeColor: UIColor?
- Stores the stroke color of the line connecting two cells -
var rows: Int
- Computes the number of rows in the canvas -
var selectedDragCell: GKCell?
- Stores the cell which is currently under draging state -
var tracedCells: [IndexPath]
- Track all the cells with the order in which they are connected, allowPathDrawing should be set to true for its working -
var updateCellOnTrace: ((GKCell, Set<UITouch>, UIEvent?) -> Void)?
- Function to call when new cell gets connected, allowPathDrawing should be set to true for its working Provides three parameters cell, touch, event -
var updateCellOnTraceEnd: ((GKCell, Set<UITouch>, UIEvent?) -> Void)?
- Function to call when event to connect two cells end to update cell properties, allowPathDrawing should be set to true for its working Provides three parameters cell, touch, event
Adds the subview in canvas at specific position and also add tag to them to identify them uniquely
func addSubview(
atRow: Int,
column: Int,
view: UIView
)
Calls GKCanvs erase() method
func eraseCanvas()
Returns the height of canvas from starting row till ending row, if ending is not know use negative indexes to get last indexes
func getHeight(
fromRow: Int,
to: Int
) -> CGFloat
Returns the size of canvas from starting (row, col) till ending (row, col), if ending is not know use negative indexes to get last indexes
func getSize(
fromGridAt: (row: Int, col: Int),
to: (row: Int, col: Int)
) -> CGSize
Returns the width of canvas from starting column till ending column, if ending is not know use negative indexes to get last indexes
func getWidth(
fromColumn: Int,
to: Int
) -> CGFloat
Calls GKCanvas populate method
func populate()
Calls GKCanvas populateCells(where:) method
func populateCells(where: ((Int, Int) -> Bool))
Calls GKCanvas populateCells(at:) method
func populteCells(at: [(row: Int, col: Int)])
Calls GKCanvas removeCell(atRow:column) method
func removeCell(
atRow: Int,
column: Int
)
Calls GKCanvas removeCells(at:) method
func removeCells(at: [(row: Int, col: Int)])
Calls GKCanvas removeCells(where:) method
func removeCells(where: ((Int, Int) -> Bool))
Resizes the canvas size to new size
func resizeCanvas(to: CGSize)
Sets the size of subview with the size of canvas from starting (row, col) till ending (row, col), if ending is not know use negative indexes to get last indexes
func setSubviewSize(
UIView,
fromGridAt startCell: (row: Int, col: Int),
to endCell: (row: Int, col: Int)
)
Calls GKCanvas updateCells(at:update:) method
func updateCells(
at: (Int, Int),
update: ((inout GKCell) -> Void)?
)
Calls GKCanvas updateCells(where:update) method
func updateCells(
where: ((Int, Int) -> Bool),
update: ((inout GKCell) -> Void)?
)
Calls GKCanvas updateCells(with:update) method
func updateCells(
with: [IndexPath],
update: ((inout GKCell) -> Void)?
)
Update the newly tracked cell properties at given position
func updateTracedCell(
atIndexPath: IndexPath,
update: ((GKCell) -> Void)?
)
To create the GKLayoutView first create the specifications for the canvas
import UIKit
import GridKit
let gkspec = GKSpec(
rows: 8,
columns: 8,
cellSize: 40,
interCellInsets: 2,
cellColor: .white,
canvasColor: .clear,
cellBorderWidth: 2,
cellBorderColor: .systemTeal,
cellCornerRadius: 8,
canvasCornerRadius: 24
)
Now as we've created the canvas specifications let's create `GKLayoutView` for our View Controller. By default canvas is placed at the center of superview
override func viewDidLoad() {
super.viewDidLoad()
let gklayout = GKLayoutView(
spec: gkspec, // Canvas specifications
in: view // Superview in which gklayout gets displayed
)
}
Now we've created our GKLayoutView we can place cells in it, to place cells in it call `populate()` method and RUN the app
gklayout.populate()
As you can see canvas gets filled with cells but what if you don't want all the cells, To get specific cells you can either use `populateCells` method or you can first fill the canvas then call `removeCells` method later on but it'll be time costly process as canvas first creates the cell and then removes the required cells so it is recommended to use `populateCells` for better performance
// gklayout.populate()
gklayout.populateCells(where: { (row, col) in
return (col == 0 && row < 5) || (row == 7)
})
Now RUN the app, you'll see only the required cells are drawn which satisfy the condition
To update the cell on specific positions use `updateCells` method
gklayout.updateCells(where: { (row, col) in
return (col == 0 && row < 5)
}, update: { cell in
cell.backgroundColor = .systemOrange
})
To check how your canvas grid is looking you can add markers in your canvas by calling `displayMarkers()` method
gklayout.canvas.displayMarkers()
To add a subview in the canvas at specific position in grid you can use `addSubview(atRow:column:view)` method
let label = UILabel()
label.text = "Hello"
label.textAlignment = .center
label.layer.borderColor = UIColor.systemOrange.cgColor
label.layer.borderWidth = 1
// setting the size of label
gklayout.setSubviewSize(label, fromGridAt: (row: 0, col: 1), to: (row: 0, col: -2))
// adding label in canvas at row 1, column 2
gklayout.addSubview(atRow: 1, column: 2, view: label)
// Now as you've added the label in the canvas try printing tag of label
// You'll notice that a number is printed so that number denotes the index of that label where it is located
// If you add another view at same row and column then that view also get's same tag
// Tag is calculated by Row-Order Indexing
// tagValue = (currentRow * totalNumberOfColumns) + currentColumn
What if you want to add a subview in a specific cell? Here is how you can achieve that
let label = UILabel()
label.text = "Hello"
label.textAlignment = .center
if let cell = self.gklayout.canvas.getCell(atRow: self.gkspec.rows-1, column: 0) {
label.font = .systemFont(ofSize: UIFont.preferredFont(forTextStyle: .caption1).pointSize)
// You'll have to manually set the size of the label using 'bounds' property
label.bounds.size = CGSize(width: cell.bounds.width-10, height: cell.bounds.height-10)
// Setting the center of the label to the cell's center using 'bounds' property
// Using 'bounds' here because if we use cell's center it'll calculate from its parent's coordinate environment
label.center = CGPoint(x: cell.bounds.width/2, y: cell.bounds.height/2)
cell.addSubview(label)
// Now try printing the 'tag' value of label
// You'll get the same tag value as the cell's tag value
}
Note: If you want to set the size prefer use 'bounds' property and for the position prefer 'frame' property because 'bounds' take coordination system of its own environment while 'frame' takes coordinate system of its parent environment
Easy Right! Now lets do something interesting, lets activate the drag and drop features in the cells
// Activate the drag and drop
gklayout.allowCellDragNDrop = true
// Now as you activated the features tell the canvas which cells are dragable and which are dropable
// Lets update our cell creation code
gklayout.updateCells(where: { (row, col) in
return (col == 0 && row < 5) || (row == 7)
}, update: { cell in
if cell.row == 7 { cell.dragable = true } // Enable all the cells to be able to draged on row 7
else { // Enable all the cells to be able to accept the drop where (col == 0 and row < 5)
cell.backgroundColor = .systemOrange
cell.dropable = true
}
})
Now RUN the app and see you can drag and drop the cells
To run specific task while droping the cell or detaching the cell you can set the properties `onCellDropped` and `onCellDetach`
gklayout.onCellDropped = { (cell: GKCell, targetCell: GKCell) in
cell.bounds.size.width -= 10
cell.bounds.size.height -= 10
cell.backgroundColor = UIColor(cgColor: targetCell.layer.borderColor!)
targetCell.backgroundColor = .white
}
gklayout.onCellDetach = { (cell: GKCell, targetCell: GKCell) in
cell.bounds.size = self.gklayout.canvas.getCellSize()
targetCell.backgroundColor = cell.backgroundColor
cell.backgroundColor = .white
}
Now RUN the app and you'll be seeing a smooth animation while draging or droping the cells
But still you may have found that you are not able to drop the cell in the empty space. To drop it in empty space activate `allowDropInEmptySpace`
gklayout.allowDropInEmptySpace = true
Now you can drop the cells in empty space too
What if you want to connect two cells with a line as you move your fingers from one cell to another? You can achieve this too by activating `allowPathDrawing`
gklayout.allowPathDrawing = true
RUN the app and see you can now getting the track of cells in which you are touching them NOTE: If allowPathDrawing is active, Drag and drop features will gets deactivated as you're already keeping track of you path
You can update the cell while connecting the cells, to do this use `updateCellOnTrace` and `updateCellOnTraceEnd`
gklayout.pathStrokeColor = .systemOrange
gklayout.updateCellOnTrace = { (cell, touch, event) in
cell.backgroundColor = self.gklayout.pathStrokeColor?.withAlphaComponent(0.5)
}
gklayout.updateCellOnTraceEnd = { (cell, touch, event) in
cell.backgroundColor = self.gklayout.canvas.gkspec.cellColor
}
If you want to update the cell or perform certain action while clicking the cell you can set `didTapCell` handler to perform custom tasks. For this you'll have to first disable the `allowPathDrawing` property and also the drag and drop property
// gklayout.allowPathDrawing = true
// gklayout.allowCellDragNDrop = true
// gklayout.allowDragInEmptySpace = true
gklayout.didTapCell = { cell in
print("Cell tapped at row: \(cell.row) and column: \(cell.column)")
UIView.animate(withDuration: 0.5) {
cell.backgroundColor = .systemOrange.withAlphaComponent(0.5)
} completion: { _ in
UIView.animate(withDuration: 0.5) {
cell.backgroundColor = self.gklayout.canvas.gkspec.cellColor
}
}
}
GridKit is available under the MIT license. See LICENSE for more details.
Symbol
: Represents a categorization of related components within GridKit.- Example:
GKCell
,GKCanvas
,GKLayoutView
might be grouped under Grid Elements. - You can define groups such as Core Structures, Layout Management, Utilities, etc.
- Example:
- Grid Elements:
GKCell
,GKCanvas
,GKLayoutView
- Specifications:
GKSpec
- Interaction Handlers: Tap gestures and event handlers.