Skip to content

Instantly share code, notes, and snippets.

@KalpeshTalkar
Last active May 17, 2017 09:11
Show Gist options
  • Save KalpeshTalkar/019a59c14f18b707d7ad3ff5a7d9cf8d to your computer and use it in GitHub Desktop.
Save KalpeshTalkar/019a59c14f18b707d7ad3ff5a7d9cf8d to your computer and use it in GitHub Desktop.
KBreadCrumbView is a custom @IBDesignable UIView component build to show bread crumbs. The component is built in Swift 2.2
//
// Copyright © 2017 Kalpesh Talkar. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// For support: https://gist.github.com/KalpeshTalkar/019a59c14f18b707d7ad3ff5a7d9cf8d
//
import Foundation
class KBreadCrumb: NSObject {
var title = ""
var subTitle = ""
init(withTitle title: String, subTitle: String) {
self.title = title
self.subTitle = subTitle
}
override func isEqual(object: AnyObject?) -> Bool {
if object is KBreadCrumb {
let obj = object as! KBreadCrumb
if obj.title == title {
return true
}
}
return false
}
}
//
// Copyright © 2017 Kalpesh Talkar. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// For support: https://gist.github.com/KalpeshTalkar/019a59c14f18b707d7ad3ff5a7d9cf8d
//
import UIKit
class KBreadCrumbCell: UICollectionViewCell {
static let id = "KBreadCrumbCell"
static let TITLE_HEIGHT: CGFloat = 15
static let SUB_TITLE_HEIGHT: CGFloat = 12
static let MARGIN: CGFloat = 4
static let ARROW_WIDTH: CGFloat = 25
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var subTitleLabel: UILabel!
@IBOutlet weak var arrowImageView: UIImageView!
@IBOutlet weak var arrowImageViewWidth: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
backgroundColor = UIColor.clearColor()
}
/*override var highlighted: Bool {
didSet {
if highlighted {
// change text color or bg color
titleLabel.textColor = UIColor.whiteColor()
subTitleLabel.textColor = UIColor.whiteColor()
} else {
// reset text color or bg color to default
titleLabel.textColor = UIColor.groupTableViewBackgroundColor()
subTitleLabel.textColor = UIColor.groupTableViewBackgroundColor()
}
}
}*/
func prepareCell(with breadCrumb: KBreadCrumb, showsArrow: Bool) {
titleLabel.textColor = UIColor.whiteColor()
subTitleLabel.textColor = UIColor.whiteColor()
titleLabel.text = breadCrumb.title
subTitleLabel.text = breadCrumb.subTitle.uppercaseString
// Flip arrowImageView horizontal
arrowImageView.transform = CGAffineTransformMakeScale(-1, 1)
arrowImageView.image = arrowImageView.image?.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
arrowImageView.tintColor = UIColor.whiteColor()
if showsArrow {
arrowImageView.hidden = false
arrowImageViewWidth.constant = KBreadCrumbCell.ARROW_WIDTH
} else {
arrowImageView.hidden = true
arrowImageViewWidth.constant = 0
}
layoutIfNeeded()
}
}
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10117" systemVersion="16D32" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" id="gTV-IL-0wX" customClass="KBreadCrumbCell" customModule="bizanalyst" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="105" height="43"/>
<autoresizingMask key="autoresizingMask"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center">
<rect key="frame" x="0.0" y="0.0" width="105" height="43"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Back.png" translatesAutoresizingMaskIntoConstraints="NO" id="XYn-e2-7kG">
<rect key="frame" x="0.0" y="9" width="19" height="25"/>
<constraints>
<constraint firstAttribute="height" constant="25" id="OaS-hV-Hha"/>
<constraint firstAttribute="width" constant="19" id="s5Z-5X-mlr"/>
</constraints>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Dell Monitor" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="v6F-zJ-blj">
<rect key="frame" x="23" y="8" width="78" height="15"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="SOCK ITEM" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cjC-LV-iUt">
<rect key="frame" x="23" y="23" width="78" height="12"/>
<fontDescription key="fontDescription" type="system" pointSize="10"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="calibratedWhite"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
</view>
<constraints>
<constraint firstAttribute="bottom" secondItem="cjC-LV-iUt" secondAttribute="bottom" constant="8" id="1h6-Pm-axw"/>
<constraint firstItem="v6F-zJ-blj" firstAttribute="top" secondItem="gTV-IL-0wX" secondAttribute="top" constant="8" id="2KE-PA-5fE"/>
<constraint firstItem="XYn-e2-7kG" firstAttribute="centerY" secondItem="gTV-IL-0wX" secondAttribute="centerY" id="7ie-h6-Obb"/>
<constraint firstItem="cjC-LV-iUt" firstAttribute="leading" secondItem="XYn-e2-7kG" secondAttribute="trailing" constant="4" id="KZa-UU-f47"/>
<constraint firstItem="XYn-e2-7kG" firstAttribute="leading" secondItem="gTV-IL-0wX" secondAttribute="leading" id="M2i-zh-Wg8"/>
<constraint firstAttribute="trailing" secondItem="cjC-LV-iUt" secondAttribute="trailing" constant="4" id="UUF-VP-PWl"/>
<constraint firstAttribute="trailing" secondItem="v6F-zJ-blj" secondAttribute="trailing" constant="4" id="kYT-cx-JUF"/>
<constraint firstItem="cjC-LV-iUt" firstAttribute="top" secondItem="v6F-zJ-blj" secondAttribute="bottom" id="m6e-TT-tom"/>
<constraint firstItem="v6F-zJ-blj" firstAttribute="leading" secondItem="XYn-e2-7kG" secondAttribute="trailing" constant="4" id="ypw-Uw-nzv"/>
</constraints>
<size key="customSize" width="130" height="44"/>
<connections>
<outlet property="arrowImageView" destination="XYn-e2-7kG" id="FEm-YZ-0Vl"/>
<outlet property="arrowImageViewWidth" destination="s5Z-5X-mlr" id="L52-dY-a04"/>
<outlet property="subTitleLabel" destination="cjC-LV-iUt" id="MqR-XT-qR3"/>
<outlet property="titleLabel" destination="v6F-zJ-blj" id="ORe-Z3-LQI"/>
</connections>
<point key="canvasLocation" x="212.5" y="290.5"/>
</collectionViewCell>
</objects>
<resources>
<image name="Back.png" width="96" height="96"/>
</resources>
</document>
//
// Copyright © 2017 Kalpesh Talkar. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// For support: https://gist.github.com/KalpeshTalkar/019a59c14f18b707d7ad3ff5a7d9cf8d
//
import UIKit
protocol KBreadCrumbViewDelegate {
func breadCrumbView(view: KBreadCrumbView, didSelectBreadCrumbAt index: Int)
}
@IBDesignable
class KBreadCrumbView: UIView, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
// MARK: - variables
private var collectionView: UICollectionView?
private var breadCrumbs = Array<KBreadCrumb>()
private var highlightedIndex: Int = 0
var delegate: KBreadCrumbViewDelegate?
#if !TARGET_INTERFACE_BUILDER
override init(frame: CGRect) {
super.init(frame: frame)
setUpCollectionView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUpCollectionView()
}
override func layoutSubviews() {
super.layoutSubviews()
if nil != collectionView {
collectionView!.frame = bounds
collectionView!.layoutSubviews()
highligtLastBreadCrumb()
}
}
#endif
// MARK: - UICollectionView Setup
private func setUpCollectionView() {
if nil == collectionView {
// UICollectionViewLayout
let layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = 0
layout.minimumInteritemSpacing = 0
layout.scrollDirection = UICollectionViewScrollDirection.Horizontal
// UICollectionView
collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
collectionView!.backgroundColor = UIColor.clearColor()
collectionView?.showsHorizontalScrollIndicator = false
collectionView!.dataSource = self
collectionView!.delegate = self
collectionView?.registerNib(UINib.init(nibName: KBreadCrumbCell.id, bundle: nil), forCellWithReuseIdentifier: KBreadCrumbCell.id)
collectionView!.backgroundColor = UIColor.clearColor()
// UIEdgeInsets
collectionView!.contentInset = UIEdgeInsetsMake(0, 10, 0, 10)
}
collectionView!.removeFromSuperview()
addSubview(collectionView!)
collectionView!.reloadData()
highligtLastBreadCrumb()
}
// MARK: - UICollectionViewDataSource
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return breadCrumbs.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(KBreadCrumbCell.id, forIndexPath: indexPath) as! KBreadCrumbCell
/*let isHighlighted = (highlightedIndex == indexPath.row)
cell.highlighted = isHighlighted*/
cell.prepareCell(with: breadCrumbs[indexPath.item], showsArrow: !(indexPath.item == 0))
return cell
}
// MARK: - UICollectionViewDelegate
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
/*highlightedIndex = indexPath.item
collectionView.reloadItemsAtIndexPaths(collectionView.indexPathsForVisibleItems())
collectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
if nil != delegate {
delegate!.breadCrumbView(self, didSelectBreadCrumbAt: indexPath.item)
}*/
}
// MARK: - UICollectionViewDelegateFlowLayout
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
let breadCrumb = breadCrumbs[indexPath.item]
// Title width
let titleLbl = UILabel()
titleLbl.font = UIFont.systemFontOfSize(12)
titleLbl.text = breadCrumb.title
let titleWidth = getWidthRequiredFor(titleLbl, height: KBreadCrumbCell.TITLE_HEIGHT)
// Sub-title width
let subTitleLbl = UILabel()
subTitleLbl.font = UIFont.systemFontOfSize(10)
subTitleLbl.text = breadCrumb.subTitle.uppercaseString
let subTitleWidth = getWidthRequiredFor(subTitleLbl, height: KBreadCrumbCell.SUB_TITLE_HEIGHT)
// Cell width
var cellWidth = titleWidth
if subTitleWidth > titleWidth {
cellWidth = subTitleWidth
}
// Add left-right margin
cellWidth += KBreadCrumbCell.MARGIN*2
if indexPath.item > 0 {
// Add arrow width
cellWidth += KBreadCrumbCell.ARROW_WIDTH
}
return CGSize(width: cellWidth, height: 43)
}
// MARK: - Helper methods
func getWidthRequiredFor(label: UILabel, height: CGFloat) -> CGFloat {
let constrainedSize = CGSize(width: CGFloat.max, height: height)
let requiredSize = label.sizeThatFits(constrainedSize)
return requiredSize.width
}
func setBreadCrumbs(breadCrumbs: Array<KBreadCrumb>) {
self.breadCrumbs = breadCrumbs
collectionView!.reloadData()
highligtLastBreadCrumb()
}
func highlightBreadCrumb(at index: Int) {
let indexPath = NSIndexPath(forItem: index, inSection: 0)
highlightedIndex = indexPath.item
if collectionView!.indexPathsForVisibleItems().count > 0 {
collectionView!.reloadItemsAtIndexPaths(collectionView!.indexPathsForVisibleItems())
}
collectionView!.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)
}
func highligtLastBreadCrumb() {
if breadCrumbs.count > 0 {
let lastIndex = breadCrumbs.count-1
highlightBreadCrumb(at: lastIndex)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment