I'm using iOS-charts by danielGindi to display a line chart. The desired behavior is to show the left-axis, x-axis and horizontal gridlines, however, sometimes one or both of the axes is missing and/or one or more of the gridlines is missing.
My code is below.
class LineChartViewController: UIViewController {
let timeIntervalCount = 15
#IBOutlet weak var lineChartView: LineChartView!
override func viewDidLoad() {
super.viewDidLoad()
configureChart()
setChartValues()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func randomizeTapped(_ sender: UIButton) {
let count = Int(arc4random_uniform(20) + 3)
setChartValues(count)
}
#IBAction func configureTapped(_ sender: UIButton) {
configureChart()
lineChartView.setNeedsDisplay()
}
#IBAction func closeTapped(_ sender: UIButton) {
dismiss (animated: true, completion: nil)
}
fileprivate func configureChart () {
lineChartView.xAxis.labelPosition = .bottom
lineChartView.xAxis.drawGridLinesEnabled = false
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.xAxis.axisLineColor = UIColor.lightGray
//lineChartView.xAxis.setLabelCount(timeIntervalCount, force: true)
lineChartView.xAxis.avoidFirstLastClippingEnabled = true
lineChartView.leftAxis.enabled = true
lineChartView.leftAxis.axisLineColor = UIColor.lightGray
lineChartView.leftAxis.drawAxisLineEnabled = true
lineChartView.leftAxis.drawGridLinesEnabled = true
lineChartView.leftAxis.gridColor = UIColor.lightGray
lineChartView.leftAxis.axisMinimum = 0
lineChartView.leftAxis.valueFormatter = self
lineChartView.rightAxis.enabled = false
lineChartView.legend.enabled = false
lineChartView.chartDescription?.enabled = false
}
fileprivate func setChartValues (_ count: Int = 15) {
let top = 23
let bottom = 8
let values = (bottom..<top).map { (i) -> ChartDataEntry in
let val = Double(arc4random_uniform(UInt32(count)) + 3)
return ChartDataEntry(x: Double(i), y: val)
}
let set1 = LineChartDataSet(values: values, label: "DataSet 1")
set1.colors = [UIColor.orange]
set1.drawValuesEnabled = false
set1.drawCirclesEnabled = false
let data = LineChartData(dataSet: set1)
self.lineChartView.data = data
}
}
extension LineChartViewController: IAxisValueFormatter {
func stringForValue(_ value: Double,
axis: AxisBase?) -> String {
return String(format: "%.0f", value) + "%"
}
}
Related
I am using Charts library for rendering line chart but I am unable to reload the data as I am trying to fetch the data from the API, there is a method given notifyDataSetChanged() but not working. I am using the chart within tableView cell. If anybody has some idea please help me out..............................................
import UIKit
import Charts
class ChartViewCell: UITableViewCell, ChartViewDelegate {
#IBOutlet weak var lineChartViewContainer: UIStackView?
var yVlaues = [ChartDataEntry]()
lazy var lineChartView: LineChartView = {
let chartView = LineChartView()
return chartView
}()
override func awakeFromNib() {
super.awakeFromNib()
setLineChart()
loadData()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func setLineChart() {
lineChartView.delegate = self
lineChartView.noDataText = "No Data Found"
lineChartView.rightAxis.enabled = false
lineChartView.xAxis.drawGridLinesEnabled = false
lineChartView.leftAxis.drawGridLinesEnabled = false
lineChartView.legend.enabled = false
//lineChartView.leftAxis.labelFont = .boldSystemFont(ofSize: 14)
lineChartView.leftAxis.labelCount = 6
lineChartView.leftAxis.labelTextColor = .black
lineChartView.xAxis.labelPosition = .bottom
let screenSize = UIScreen.main.bounds
let screenWidth = screenSize.width
lineChartView.frame = CGRect(x: 10, y: 70, width: screenWidth - 20, height: 200)
setData()
self.addSubview(lineChartView)
}
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
print("Abhay x \(entry.x) y \(entry.y)")
}
func setData() {
let set1 = LineChartDataSet(entries: yVlaues)
set1.lineWidth = 1
set1.colors = [UIColor.colorFromHex(hexString: "#80005661")]
set1.drawVerticalHighlightIndicatorEnabled = true
set1.drawHorizontalHighlightIndicatorEnabled = false
set1.highlightColor = UIColor.colorFromHex(hexString: "#80005661")
set1.highlightLineWidth = 1.0
set1.drawValuesEnabled = false
set1.circleHoleColor = .white
set1.circleColors = [UIColor.colorFromHex(hexString: "#80005661")]
set1.circleRadius = 5
let data = LineChartData(dataSet: set1)
lineChartView.data = data
let startColor = UIColor.colorFromHex(hexString: "#80005661").cgColor
let endColor = UIColor.white.cgColor
let gradientColors = [startColor, endColor] as CFArray // Colors of the gradient
let colorLocations:[CGFloat] = [1.0, 0.0] // Positioning of the gradient
let gradient = CGGradient.init(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: gradientColors, locations: colorLocations) // Gradient Object
if let gradient = gradient {
set1.fill = Fill.fillWithLinearGradient(gradient, angle: 90.0) // Set the Gradient
}
set1.drawFilledEnabled = true // Draw the Gradient
}
func loadData() {
var params = [String: Any]()
guard let userId = Reusable.getUserInfo()?.id else { return }
params = ["user_id": userId]
WebServiceHelper.postWebServiceCall(Constants.baseURL + "performedWorkoutsGraph", params: params, isShowLoader: false, success: { (responceObj) in
let statusMsg = StatusBool(json: responceObj)
if statusMsg.status {
self.yVlaues.removeAll()
let responseJsonArr = responceObj["data"].arrayValue
for item in responseJsonArr {
let graphData = GraphDataModel(json: item)
let chartDataEntry = ChartDataEntry(x: Double(graphData.interval), y: Double(graphData.workouts))
self.yVlaues.append(chartDataEntry)
}
self.lineChartView.data?.notifyDataChanged()
self.lineChartView.notifyDataSetChanged()
} else {
CommonUtils.showToastMessage(message: statusMsg.message)
}
}
, failure: { (failure) in
print(failure)
})
}
}
I just found the solution but not the reloading tricks I m calling setData() inside my loadData() and it's working
I have a StackContainerView inside my main view controller called TodayPicksViewController. I am trying to programmatically set the StackContainerView to fill up the whole view controller side to side, with around 50 from top and bottom (just like a Tinder card).
However, as I try to implement constraints relative to safe area as follows(as other answers on StackOverflow suggest), turned out the StackContainerView doesn't show up at all. I don't know where the problem is.
Please advice.
Code of my main view controller, TodayPicksViewController:
class TodayPicksViewController: UIViewController {
//MARK: - Properties
var viewModelData = [CardsDataModel(bgColor: UIColor(red:0.96, green:0.81, blue:0.46, alpha:1.0), text: "Hamburger", image: "hamburger"),
CardsDataModel(bgColor: UIColor(red:0.29, green:0.64, blue:0.96, alpha:1.0), text: "Puppy", image: "puppy"),
CardsDataModel(bgColor: UIColor(red:0.29, green:0.63, blue:0.49, alpha:1.0), text: "Poop", image: "poop"),
CardsDataModel(bgColor: UIColor(red:0.69, green:0.52, blue:0.38, alpha:1.0), text: "Panda", image: "panda"),
CardsDataModel(bgColor: UIColor(red:0.90, green:0.99, blue:0.97, alpha:1.0), text: "Subway", image: "subway"),
CardsDataModel(bgColor: UIColor(red:0.83, green:0.82, blue:0.69, alpha:1.0), text: "Robot", image: "robot")]
var stackContainer : StackContainerView!
private let spinner = JGProgressHUD(style: .dark)
private var users = [[String: String]]()
private var results = [SearchResult]()
private var hasFetched = false
var divisor: CGFloat!
private let noResultsLabel: UILabel = {
let label = UILabel()
label.isHidden = true
label.text = "No Results"
label.textAlignment = .center
label.textColor = .green
label.font = .systemFont(ofSize: 21, weight: .medium)
return label
}()
override func loadView() {
view = UIView()
stackContainer = StackContainerView()
view.addSubview(stackContainer)
stackContainer.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(noResultsLabel)
configureStackContainer()
stackContainer.dataSource = self
}
#IBAction func panMatch(_ sender: UIPanGestureRecognizer) {
let match = sender.view!
let point = sender.translation(in: view)
let xFromCenter = match.center.x - view.center.x
print(xFromCenter)
match.center = CGPoint(x: view.center.x + point.x, y: view.center.y + point.y)
match.transform = CGAffineTransform(rotationAngle: xFromCenter/divisor)
if sender.state == UIGestureRecognizer.State.ended {
if match.center.x < 75 {
// Move off to the left side
UIView.animate(withDuration: 0.3, animations: {
match.center = CGPoint(x: match.center.x - 200, y: match.center.y + 75)
match.alpha = 0
})
return
} else if match.center.x > (view.frame.width - 75) {
// Move off to the right side
UIView.animate(withDuration: 0.3, animations: {
match.center = CGPoint(x: match.center.x + 200, y: match.center.y + 75)
match.alpha = 0
})
return
}
// resetCard()
}
}
private var loginObserver: NSObjectProtocol?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
validateAuth()
}
private func validateAuth() {
if FirebaseAuth.Auth.auth().currentUser == nil {
let vc = SignInViewController()
let nav = UINavigationController(rootViewController: vc)
nav.modalPresentationStyle = .fullScreen
present(nav, animated: false)
}
}
#objc private func pageControlDidChange(_ sender: UIPageControl) {
let current = sender.currentPage
// scrollView.setContentOffset(CGPoint(x: CGFloat(current) * view.frame.size.width,
// y: 70), animated: true)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
//MARK: - Configurations
func configureStackContainer() {
stackContainer.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackContainer.centerYAnchor.constraint(equalTo: self.view.centerYAnchor, constant: -60).isActive = true
// stackContainer.widthAnchor.constraint(equalToConstant: 300).isActive = true
// stackContainer.heightAnchor.constraint(equalToConstant: 400).isActive = true
stackContainer.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
stackContainer.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor).isActive = true
stackContainer.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor).isActive = true
stackContainer.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor).isActive = true
}
func updateUI() {
if results.isEmpty {
noResultsLabel.isHidden = false
}
else {
noResultsLabel.isHidden = true
}
}
func calcAge(birthday: Date) -> Int {
let dateFormater = DateFormatter()
dateFormater.dateFormat = "MM/dd/yyyy"
// let birthdayDate = dateFormater.date(from: birthday)
let calendar: NSCalendar! = NSCalendar(calendarIdentifier: .gregorian)
let now = Date()
let calcAge = calendar.components(.year, from: birthday, to: now, options: [])
let age = calcAge.year
return age!
}
extension TodayPicksViewController : SwipeCardsDataSource {
func numberOfCardsToShow() -> Int {
return viewModelData.count
}
func card(at index: Int) -> SwipeCardView {
let card = SwipeCardView()
card.dataSource = viewModelData[index]
return card
}
func emptyView() -> UIView? {
return nil
}
}
Probably doesn't matter, but here is my code for the StackContainerView:
class StackContainerView: UIView, SwipeCardsDelegate {
//MARK: - Properties
var numberOfCardsToShow: Int = 0
var cardsToBeVisible: Int = 3
var cardViews : [SwipeCardView] = []
var remainingcards: Int = 0
let horizontalInset: CGFloat = 10.0
let verticalInset: CGFloat = 10.0
var visibleCards: [SwipeCardView] {
return subviews as? [SwipeCardView] ?? []
}
var dataSource: SwipeCardsDataSource? {
didSet {
reloadData()
}
}
//MARK: - Init
override init(frame: CGRect) {
super.init(frame: .zero)
backgroundColor = .clear
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func reloadData() {
removeAllCardViews()
guard let datasource = dataSource else { return }
setNeedsLayout()
layoutIfNeeded()
numberOfCardsToShow = datasource.numberOfCardsToShow()
remainingcards = numberOfCardsToShow
for i in 0..<min(numberOfCardsToShow,cardsToBeVisible) {
addCardView(cardView: datasource.card(at: i), atIndex: i )
}
}
//MARK: - Configurations
private func addCardView(cardView: SwipeCardView, atIndex index: Int) {
cardView.delegate = self
addCardFrame(index: index, cardView: cardView)
cardViews.append(cardView)
insertSubview(cardView, at: 0)
remainingcards -= 1
}
func addCardFrame(index: Int, cardView: SwipeCardView) {
var cardViewFrame = bounds
let horizontalInset = (CGFloat(index) * self.horizontalInset)
let verticalInset = CGFloat(index) * self.verticalInset
cardViewFrame.size.width -= 2 * horizontalInset
cardViewFrame.origin.x += horizontalInset
cardViewFrame.origin.y += verticalInset
cardView.frame = cardViewFrame
}
private func removeAllCardViews() {
for cardView in visibleCards {
cardView.removeFromSuperview()
}
cardViews = []
}
func swipeDidEnd(on view: SwipeCardView) {
guard let datasource = dataSource else { return }
view.removeFromSuperview()
if remainingcards > 0 {
let newIndex = datasource.numberOfCardsToShow() - remainingcards
addCardView(cardView: datasource.card(at: newIndex), atIndex: 2)
for (cardIndex, cardView) in visibleCards.reversed().enumerated() {
UIView.animate(withDuration: 0.2, animations: {
cardView.center = self.center
self.addCardFrame(index: cardIndex, cardView: cardView)
self.layoutIfNeeded()
})
}
}else {
for (cardIndex, cardView) in visibleCards.reversed().enumerated() {
UIView.animate(withDuration: 0.2, animations: {
cardView.center = self.center
self.addCardFrame(index: cardIndex, cardView: cardView)
self.layoutIfNeeded()
})
}
}
}
}
According to the apple developer doc for loadView(), they said "The view controller calls this method when its view property is requested but is currently nil. This method loads or creates a view and assigns it to the view property." This might be the cause of the problem. I would recommend you to perform the view set up operations in viewDidLoad or other proper lifecycle methods. Based on my understanding, this line view = UIView() isn't necessary. In your configureStackContainer() func, you set the centerX and centerY anchor and then set the top, leading, trailing, bottom anchor again. This may also raise the constraint conflicts. I think you don't need to specify centerX and centerY anchor if you want to constraint with top, leading, trailing and bottom and vice versa. I hope this will be helpful.
I am using iOS- Charts, and i created bar chart. I also conformed the delegate so that i can get callbacks when i am tapped on a bar but the problem is when i tapped in the non bar area also the delegate method is getting called and getting highlighted. I am using Swift3.
var mMonths : [String]?
var mAverage : Double?
var mValues : [Double]?
override func viewDidLoad() {
super.viewDidLoad()
mMonths = ["A","B","C","D","F","G","H","I","J","K","L","M"]
}
override func viewWillAppear(_ animated: Bool) {
mAverage = 50
mValues = [20,40.0,0,50,10,100,55,80,40,10.50,80,35]
mBarChartView.delegate = self
setChart(dataPoints: mMonths!, values: mValues!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// For setting up the data and customizing the Chart
private func setChart(dataPoints :[String] , values : [Double])
{
var dataEntries : [BarChartDataEntry] = []
var colors : [UIColor] = []
let belowAverageColor : UIColor = UIColor.blue
let aboveAverageColor : UIColor = UIColor.blue.withAlphaComponent(0.5)
let averageColor : UIColor = UIColor.lightGray
for i in 0..<dataPoints.count
{
let eachValue : Double = values[i]
var entry : BarChartDataEntry?
if eachValue == 0
{
entry = BarChartDataEntry.init(x: Double(i), yValues: [mAverage!])
colors.append(averageColor)
}
else if eachValue <= mAverage!
{
entry = BarChartDataEntry.init(x: Double(i), yValues: [eachValue,mAverage!-eachValue])
colors.append(belowAverageColor)
colors.append(averageColor)
}
else
{
entry = BarChartDataEntry.init(x: Double(i), yValues: [mAverage!,eachValue-mAverage!])
colors.append(belowAverageColor)
colors.append(aboveAverageColor)
}
dataEntries.append(entry!)
}
let dataSet = BarChartDataSet.init(values: dataEntries, label: "")
// removed value on top of each bar
dataSet.drawValuesEnabled = false
// removing the highlight on bar tapped
dataSet.highlightAlpha = 0
// assigning colors to bar
dataSet.colors = colors
let data = BarChartData(dataSet: dataSet)
mBarChartView.data = data
// Skipping labels in between
mBarChartView.xAxis.setLabelCount(mMonths!.count, force: false)
// setting data on X axis
mBarChartView.xAxis.valueFormatter = IndexAxisValueFormatter.init(values: mMonths!)
// color of labels on xaxis
mBarChartView.xAxis.labelTextColor = UIColor.black
// setting maximum value of the graph
mBarChartView.leftAxis.axisMaximum = 100
mBarChartView.rightAxis.axisMaximum = 100
mBarChartView.leftAxis.axisMinimum = 0.0
mBarChartView.rightAxis.axisMinimum = 0.0
// removing grid lines
mBarChartView.leftAxis.drawGridLinesEnabled = false
mBarChartView.rightAxis.drawGridLinesEnabled = false
mBarChartView.xAxis.drawGridLinesEnabled = false
// removing left and right axis
mBarChartView.leftAxis.enabled = false
mBarChartView.rightAxis.enabled = false
// removing bottom line
mBarChartView.xAxis.drawAxisLineEnabled = false
// Emptying the description label
mBarChartView.chartDescription?.text = ""
// placing the X axis label to bottom
mBarChartView.xAxis.labelPosition = .bottom
// bottom information about the bars is hidden
mBarChartView.legend.enabled = false
// Disabling the Zooming
mBarChartView.doubleTapToZoomEnabled = false
mBarChartView.pinchZoomEnabled = true
mBarChartView.scaleXEnabled = true
mBarChartView.scaleYEnabled = false
mBarChartView.highlightFullBarEnabled = true
}
Thank you
check this image
In the ChartHighlighter.swift file you can change the closestSelectionDetailByPixel function to this:
internal func closestSelectionDetailByPixel(
closestValues: [Highlight],
x: CGFloat,
y: CGFloat,
axis: YAxis.AxisDependency?,
minSelectionDistance: CGFloat) -> Highlight?
{
var distance = minSelectionDistance
var closest: Highlight?
for i in 0 ..< closestValues.count
{
let high = closestValues[i]
if axis == nil || high.axis == axis
{
print("high.xPx: \(high.xPx)")
print("high.x: \(high.x)")
print("high.yPx: \(high.yPx)")
print("high.y: \(high.y)")
print("x: \(x)")
print("y: \(y)")
let cDistance = getDistance(x1: x, y1: y, x2: high.xPx, y2: high.yPx)
print("cDistance: \(cDistance)")
if cDistance < distance
{
closest = high
distance = cDistance
//if the y position where user clicks is above the value, return nil
if(y<high.yPx) {
print("returning nil")
return nil
}
}
}
}
print("closest: \(closest)")
return closest
}
The only thing I added was this:
//if the y position where user clicks is above the value, return nil
if(y<high.yPx) {
print("returning nil")
return nil
}
and the print statements, if you need to do more changes you can use them to understand better how the function works. Otherwise just delete the print statements.
I'm using Daniel Gindi's Charts library (iOS-charts), and in order to keep things simple I'm using a custom UIView to store the chart view (as a ChartViewBase). I have a class hierarchy of View Controller classes where the leaf class has the reference to the UIView with the ChartViewBase in it. The problem is that the chart is refusing to display, all I get is a black rectangle where the chart is supposed to be. I guess the code would probably be the most useful, so here goes nothing:
The generic UIView chart-holding class:
import UIKit
import Charts
protocol GenericChartViewDelegate: class {
func backButtonTapped()
func switchChartButtonTapped()
func finalizeResultsButtonTapped()
}
class GenericChartView: UIView, ChartViewDelegate
{
#IBOutlet weak var headerDisplay: UIButton!
#IBOutlet var view: UIView!
weak var delegate: GenericChartViewDelegate?
required init?(coder aDecoder: NSCoder)
{
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("GenericChartView", owner: self, options: nil)
self.addSubview(self.view)
chart.delegate = self
}
override init(frame: CGRect)
{
super.init(frame: frame)
Bundle.main.loadNibNamed("GenericChartView", owner: self, options: nil)
self.addSubview(self.view)
chart.delegate = self
}
#IBOutlet var chart: ChartViewBase!
#IBAction func BackButton(_ sender: UIButton) {
delegate?.backButtonTapped()
}
#IBAction func SwitchChartButton(_ sender: UIButton) {
delegate?.switchChartButtonTapped()
}
#IBAction func FinalizeResultsButton(_ sender: UIButton) {
delegate?.finalizeResultsButtonTapped()
}
func setHeader(header: String)
{
headerDisplay.setTitle(header, for: UIControlState.normal)
headerDisplay.layer.cornerRadius = MyVariables.borderCurvature
headerDisplay.titleLabel?.adjustsFontSizeToFitWidth = true
}
}
Now the base chart view controller:
class ChartViewControllerBase: UIViewController
{
var myColors : [NSUIColor] = []
//var bounds : CGRect!
override func viewDidLoad() {
super.viewDidLoad()
let sessionCount = ShareData.sharedInstance.currentSessionNumber
//NotificationCenter.default.addObserver(self, selector: "rotated", name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
createChart()
var myBase = getChartObject()
var myChartView = getChartDisplayView()
setDelegate()
if let myName = ShareData.sharedInstance.currentAccount.name {//sessionName.characters.last!
let myTitle = "Session " + "\(sessionCount)" + " Results - " + myName
myChartView.setHeader(header: myTitle)
}
//chartOriginalBounds = chart.bounds
if let resultChoice = ShareData.sharedInstance.sessionDataObjectContainer[ShareData.sharedInstance.currentAccountSessionNames[sessionCount - 1]]?.chartInfo
{
makeChart(resultChoice: resultChoice)
}
// Do any additional setup after loading the view.
}
func setDelegate()
{
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func createChart()
{
}
func getChartDisplayView() -> GenericChartView
{
return GenericChartView(frame: self.view.bounds)
}
func getChartObject() -> ChartViewBase
{
return ChartViewBase()
}
func makeChart(resultChoice: chartData)
{
}
func getColors(value: Double)
{
}
func getChartDataEntry(xval: Double, yval: Double) -> ChartDataEntry
{
return ChartDataEntry(x: xval, y: yval)
}
}
Now the LineAndBarChart code:
class LineAndBarChartViewControllerBase: ChartViewControllerBase {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func getChartObject() -> ChartViewBase
{
return ChartViewBase()
}
var yAxis: YAxis!
func setYAxisFormatter() {
}
func createSet(myEntries: [ChartDataEntry])
{
}
override func makeChart(resultChoice: chartData)
{
let chart = getChartObject() as! BarLineChartViewBase
chart.chartDescription?.enabled = false
chart.drawGridBackgroundEnabled = false
chart.dragEnabled = true
chart.setScaleEnabled(true)
chart.pinchZoomEnabled = true
let xAxis = chart.xAxis
xAxis.labelPosition = XAxis.LabelPosition.bottom
chart.rightAxis.enabled = false
xAxis.drawGridLinesEnabled = false
xAxis.drawAxisLineEnabled = true
xAxis.granularity = 1.0
//xAxis.setValuesForKeys(<#T##keyedValues: [String : Any]##[String : Any]#>)
yAxis = chart.leftAxis
chart.rightAxis.enabled = false
chart.legend.enabled = true
yAxis.granularity = 1.0
yAxis.drawGridLinesEnabled = false
var counter = 1.0
var myEntries : [ChartDataEntry] = []
var myXValues : [String] = []
if !resultChoice.plotPoints.isEmpty
{
for var item in resultChoice.plotPoints
{
let timeString : String =
{
let formatter = DateFormatter()
formatter.dateFormat = "h:mm a"
formatter.amSymbol = "AM"
formatter.pmSymbol = "PM"
return formatter.string(from: item.xValue)
}()
myXValues.append(timeString)
/*if(item.showXValue)
{
myXValues.append(timeString)
}
else
{
myXValues.append("")
}*/
let myEntry = getChartDataEntry(xval: counter, yval: Double(item.yValue))
getColors(value: myEntry.y)
counter += 1
myEntries.append(myEntry)
}
let myFormatter = MyXAxisValueFormatter(values: myXValues)
xAxis.valueFormatter = myFormatter
setYAxisFormatter()
createSet(myEntries: myEntries)
}
}
}
class MyXAxisValueFormatter: NSObject, IAxisValueFormatter {
var mValues: [String]
init(values: [String]) {
self.mValues = values;
}
func stringForValue( _ value: Double, axis: AxisBase?) -> String
{
// "value" represents the position of the label on the axis (x or y)
let myVal: Int = Int(value)
if (myVal < mValues.count)
{
return mValues[myVal];
}
else
{
return "ERROR"
}
}
}
Then the bar chart base code:
class BarChartViewControllerBase: LineAndBarChartViewControllerBase {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func createChart()
{
var myChart = getChartDisplayView()
myChart.chart = BarChartView(frame: myChart.chart.contentRect)
}
override func createSet(myEntries: [ChartDataEntry])
{
let chart = getChartObject() as! BarChartView
let mySet = BarChartDataSet(values: myEntries, label: "Values")
mySet.colors = myColors
mySet.valueColors = myColors
setValueFormatter(set: mySet)
let myBarChartData = BarChartData(dataSet: mySet)
myBarChartData.barWidth = 0.8
chart.data = myBarChartData
}
func setValueFormatter(set: BarChartDataSet)
{
}
override func getChartDataEntry(xval: Double, yval: Double) -> ChartDataEntry
{
return BarChartDataEntry(x: xval, y: yval)
}
}
And finally, the leaf (most derived class) code:
class DerivedClassChartViewController: BarChartViewControllerBase, GenericChartViewDelegate
{
#IBOutlet weak var chartView: GenericChartView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func getChartDisplayView() -> GenericChartView
{
return chartView
}
override func getChartObject() -> ChartViewBase
{
return chartView.chart
}
override func getColors(value: Double)
{
if (value == 0.0)
{
myColors.append(UIColor.black)
}
else if (value == 1.0)
{
myColors.append(MyVariables.colorOne)
}
else if (value == 2.0)
{
myColors.append(MyVariables.colorTwo)
}
else if (value == 3.0)
{
myColors.append(MyVariables.colorThree)
}
else if (value == 4.0)
{
myColors.append(MyVariables.colorFour)
}
else if (value == 5.0)
{
myColors.append(MyVariables.colorFive)
}
}
func backButtonTapped()
{
self.navigationController?.popViewController(animated: false)
}
func switchChartButtonTapped()
{
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertControllerStyle.actionSheet)
let goToLineChartAction = UIAlertAction(title: "Line Chart", style: UIAlertActionStyle.default)
{ (alertAction: UIAlertAction!) -> Void in
self.navigationController?.popViewController(animated: false)
let viewControllerStoryboardId = "LineChartDisplayViewController"
let storyboardName = "Main"
let storyboard = UIStoryboard(name: storyboardName, bundle: Bundle.main)
let viewController = storyboard.instantiateViewController(withIdentifier: viewControllerStoryboardId)
self.navigationController?.pushViewController(viewController, animated: false)
}
alertController.addAction(goToLineChartAction)
alertController.view.tintColor = UIColor.black
present(alertController, animated: true, completion: nil)
let presentationController = alertController.popoverPresentationController
presentationController?.sourceView = self.view
presentationController?.sourceRect = CGRect(x: 330, y: 210, width: 330, height: 210)
}
func finalizeResultsButtonTapped()
{
}
override func setDelegate()
{
chartView.delegate = self
}
override func setValueFormatter(set: BarChartDataSet)
{
set.valueFormatter = DerivedClassNumberFormatter()
}
}
class DerivedClassNumberFormatter: NSObject, IValueFormatter {
func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
var returnVal = "\(Int(value))"
return returnVal
}
}
I do have a few questions. Do you make the UIView the ChartViewDelegate, or should that be the view controller that holds the UIView that holds the chart object? Am I missing anything obvious? I've used custom UIViews before, so I know that that code is correct (aside from whether the class should be ChartViewDelegate or not, that is- that I don't know). Yes I do need all of these classes, as I will have many, many chart types in the future. Anyhow, any suggestions on what I can do to get the chart to display would be great.
Thanks,
Sean
I also am using the Charts library. I am not sure if I quite understand your limitation, but I was able to include Charts inside of a custom UIView. I have other methods (not shown) to pass actual data to the Labels and Charts from the View Controller.
Here's how I did it:
import UIKit
import Charts
class MyHeader: UIView, ChartViewDelegate {
// My Header is the top view of the ViewController
// It gives summary information for the user
// MARK: - Properties
var firstLabel = LabelView()
var secondLabel = LabelView()
var firstPie = PieChartView()
var secondPie = PieChartView()
var thirdPie = PieChartView()
let nformatter = NumberFormatter()
// MARK: - LifeCycle
override func awakeFromNib() {
super.awakeFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.white.withAlphaComponent(0.5)
setupStack()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Setup Methods
func setupLabels() {
let lbls = [firstLabel, secondLabel]
for lbl in lbls {
lbl.label.font = UIFont.preferredFont(forTextStyle: .title3)
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
let pies = [firstPie, secondPie, thirdPie]
for pie in pies {
pie.translatesAutoresizingMaskIntoConstraints = false
pie.heightAnchor.constraint(equalToConstant: 100).isActive = true
pie.spanSuperView() // spanSuperView is a UIView extension that I use
pie.delegate = self as ChartViewDelegate
// entry label styling
pie.entryLabelColor = .white
pie.entryLabelFont = .systemFont(ofSize: 12, weight: .light)
pie.animate(xAxisDuration: 1.4, easingOption: .easeOutBack)
}
}
func setupFake() {
let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]
let unitsSold = [20.0, 4.0, 6.0, 3.0, 12.0, 16.0]
setChart(dataPoints: months, values: unitsSold)
}
func setChart(dataPoints: [String], values: [Double]) {
var dataEntries: [ChartDataEntry] = []
for i in 0..<dataPoints.count {
let dataEntry = ChartDataEntry(x: values[i], y: Double(i))
dataEntries.append(dataEntry)
}
let set = PieChartDataSet(values: dataEntries, label: "Results")
set.drawIconsEnabled = false
set.sliceSpace = 2
set.colors = ChartColorTemplates.joyful()
+ ChartColorTemplates.colorful()
+ ChartColorTemplates.liberty()
+ ChartColorTemplates.pastel()
+ [UIColor(red: 51/255, green: 181/255, blue: 229/255, alpha: 1)]
let data = PieChartData(dataSet: set)
let pies = [firstPie, secondPie, thirdPie]
for pie in pies {
pie.data = data
}
}
func setupStack(){
setupFake()
setupLabels()
let dashStack = UIStackView(arrangedSubviews: [firstPie, secondPie, thirdPie])
dashStack.axis = .horizontal
dashStack.distribution = .fillEqually
dashStack.alignment = .center
dashStack.spacing = 0
dashStack.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView(arrangedSubviews: [firstLabel, secondLabel, dashStack])
stackView.axis = .vertical
stackView.distribution = .fill
stackView.alignment = .fill
stackView.spacing = 0
stackView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(stackView)
//autolayout the stack view
stackView.spanSuperView()
}
}
I want to swipe each image to switch to another image like gallery app. I am now using this https://github.com/nicklockwood/SwipeView, but I don't know how to implement it. Should I drag a collection view inside my PhotoDetailViewController, or I only use it in coding. May anyone help me with this.
Here is my code:
import Foundation
import UIKit
import AAShareBubbles
import SwipeView
class PhotoDetailViewController: UIViewController, AAShareBubblesDelegate, SwipeViewDataSource, SwipeViewDelegate {
#IBOutlet var topView: UIView!
#IBOutlet var bottomView: UIView!
#IBOutlet var photoImageView: UIImageView!
var photoImage = UIImage()
var checkTapGestureRecognize = true
var swipeView: SwipeView = SwipeView.init(frame: CGRect(x: 0, y: 0, width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height))
override func viewDidLoad() {
title = "Photo Detail"
super.viewDidLoad()
photoImageView.image = photoImage
swipeView.dataSource = self
swipeView.delegate = self
let swipe = UISwipeGestureRecognizer(target: self, action: "swipeMethod")
swipeView.addGestureRecognizer(swipe)
swipeView.addSubview(photoImageView)
swipeView.pagingEnabled = false
swipeView.wrapEnabled = true
}
func swipeView(swipeView: SwipeView!, viewForItemAtIndex index: Int, reusingView view: UIView!) -> UIView! {
return photoImageView
}
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return images.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("SwipeCell", forIndexPath: indexPath) as! SwipeViewPhotoCell
return cell
}
#IBAction func onBackClicked(sender: AnyObject) {
self.navigationController?.popViewControllerAnimated(true)
}
#IBAction func onTabGestureRecognize(sender: UITapGestureRecognizer) {
print("on tap")
if checkTapGestureRecognize == true {
bottomView.hidden = true
topView.hidden = true
self.navigationController?.navigationBarHidden = true
let screenSize: CGRect = UIScreen.mainScreen().bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
photoImageView.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
checkTapGestureRecognize = false
showAminationOnAdvert()
}
else if checkTapGestureRecognize == false {
bottomView.hidden = false
topView.hidden = false
self.navigationController?.navigationBarHidden = false
checkTapGestureRecognize = true
}
}
func showAminationOnAdvert() {
let transitionAnimation = CATransition();
transitionAnimation.type = kCAEmitterBehaviorValueOverLife
transitionAnimation.subtype = kCAEmitterBehaviorValueOverLife
transitionAnimation.duration = 2.5
transitionAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transitionAnimation.fillMode = kCAFillModeBoth
photoImageView.layer.addAnimation(transitionAnimation, forKey: "fadeAnimation")
}
#IBAction func onShareTouched(sender: AnyObject) {
print("share")
let myShare = "I am feeling *** today"
let shareVC: UIActivityViewController = UIActivityViewController(activityItems: [myShare], applicationActivities: nil)
self.presentViewController(shareVC, animated: true, completion: nil)
// print("share bubles")
// let shareBubles: AAShareBubbles = AAShareBubbles.init(centeredInWindowWithRadius: 100)
// shareBubles.delegate = self
// shareBubles.bubbleRadius = 40
// shareBubles.sizeToFit()
// //shareBubles.showFacebookBubble = true
// shareBubles.showTwitterBubble = true
// shareBubles.addCustomButtonWithIcon(UIImage(named: "twitter"), backgroundColor: UIColor.whiteColor(), andButtonId: 100)
// shareBubles.show()
}
#IBAction func playAutomaticPhotoImages(sender: AnyObject) {
animateImages(0)
}
func animateImages(no: Int) {
var number: Int = no
if number == images.count - 1 {
number = 0
}
let name: String = images[number]
self.photoImageView!.alpha = 0.5
self.photoImageView!.image = UIImage(named: name)
//code to animate bg with delay 2 and after completion it recursively calling animateImage method
UIView.animateWithDuration(2.0, delay: 0.8, options:UIViewAnimationOptions.CurveEaseInOut, animations: {() in
self.photoImageView!.alpha = 1.0;
},
completion: {(Bool) in
number++;
self.animateImages(number);
print(String(images[number]))
})
}
}
Just drag and drop a UIView to your storyboard/XIB, and set its customclass to SwipeView.
Also set the delegate and datasource to the view controller which includes the UIView you just dragged.
Then in the viewcontroller, implement the required delegate methods similar to how you'd implement the methods for a tableview.