I am new to IOS development. Please help me with issue. I am not getting data on Y axis. Fetching data through Alamofire using webAPI. Data is coming nil. Same code is working fine with UIViewController table. I am getting the response. But when using in Graph it is not working.
Issue: I am using Alamofire to parse WebAPI. 'SLPercent' is the value which i want to display on Y-axis.But xml value coming as nil.
class ViewController: UIViewController, ScrollableGraphViewDataSource {
var xml = try! XML.parse("")
var graphView: ScrollableGraphView!
var currentGraphType = GraphType.bar
var graphConstraints = [NSLayoutConstraint]()
var label = UILabel()
var reloadLabel = UILabel()
// Data for the different plots
var numberOfDataItems = day
// Data for graphs with a single plot
/*lazy var simpleLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 100, shouldIncludeOutliers: false)
*/
lazy var barPlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 100, shouldIncludeOutliers: false)
// Data for graphs with multiple plots
lazy var blueLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 50)
// lazy var orangeLinePlotData: [Double] = self.generateRandomData(self.numberOfDataItems!, max: 40, shouldIncludeOutliers: false)
// Init
override func viewDidLoad() {
super.viewDidLoad()
let user = "ndbd#gmail.com"
let passwort = "xdc"
var url = URL(string: "https://ceef")
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData
configuration.timeoutIntervalForResource = 10
let credentialData = "\(user):\(passwort)".data(using: String.Encoding.utf8)!
let base64Credentials = credentialData.base64EncodedString(options: [])
let headers = ["Accept": "application/xml","Authorization": "Basic \(base64Credentials)"]
DispatchQueue.main.async {
Alamofire.request(
url!,
method: .get,
parameters: nil,
encoding: URLEncoding.default,
headers:headers)
.responseString
{ response in
debugPrint(response)
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if response.result.value != nil
{
self.xml = try! XML.parse(response.result.value!)
}
}
}
// self.CallWebAPI()
// Labels for the x-axis
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let nameOfMonth = dateFormatter.string(from: now)
var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth);
graphView = createMultiPlotGraphOne(self.view.frame)
graphView.topMargin = 200
graphView.bottomMargin = 20
addReloadLabel(withText: "RELOAD")
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "⬅", style: .plain, target: self, action: #selector(backAction))
}
func backAction()
{
//print("Back Button Clicked")
dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewDidLoad()
// self.CallWebAPI()
graphView = createMultiPlotGraphOne(self.view.frame)
graphView.topMargin = 200
graphView.bottomMargin = 20
addReloadLabel(withText: "RELOAD")
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidLoad()
graphView = createMultiPlotGraphOne(self.view.frame)
graphView.topMargin = 200
graphView.bottomMargin = 20
addReloadLabel(withText: "RELOAD")
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
}
// Implementation for ScrollableGraphViewDataSource protocol
// #########################################################
// You would usually only have a couple of cases here, one for each
// plot you want to display on the graph. However as this is showing
// off many graphs with different plots, we are using one big switch
// statement.
func value(forPlot plot: Plot, atIndex pointIndex: Int) -> Double {
switch("bar") {
// Data for the graphs with a single plot
case "bar":
return barPlotData[pointIndex]
default:
return 30
}
}
func label(atIndex pointIndex: Int) -> String {
// Ensure that you have a label to return for the index
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let nameOfMonth = dateFormatter.string(from: now)
var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth);
return xAxisLabels[pointIndex]
}
func numberOfPoints() -> Int {
return numberOfDataItems!
}
// Creating Different Kinds of Graphs
// min: 0
// max: 100
// Will not adapt min and max reference lines to range of visible points
private func createBarGraph(_ frame: CGRect) -> ScrollableGraphView {
let graphView = ScrollableGraphView(frame: frame, dataSource: self)
graphView.topMargin = 200
graphView.bottomMargin = 20
// Setup the plot
let barPlot = BarPlot(identifier: "bar")
barPlot.barWidth = 25
barPlot.barLineWidth = 1
barPlot.barLineColor = UIColor.colorFromHex(hexString: "#777777")
barPlot.barColor = UIColor.colorFromHex(hexString: "#555555")
barPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic
barPlot.animationDuration = 1.5
// Setup the reference lines
let referenceLines = ReferenceLines()
referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8)
referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2)
referenceLines.referenceLineLabelColor = UIColor.white
referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(0.5)
// Setup the graph
graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333")
graphView.shouldAnimateOnStartup = true
graphView.rangeMax = 100
graphView.rangeMin = 0
// Add everything
graphView.addPlot(plot: barPlot)
graphView.addReferenceLines(referenceLines: referenceLines)
return graphView
}
fileprivate func createMultiPlotGraphOne(_ frame: CGRect) -> ScrollableGraphView {
let graphView = ScrollableGraphView(frame: frame, dataSource: self)
graphView.topMargin = 200
graphView.bottomMargin = 20
// Setup the first plot.
let blueLinePlot = LinePlot(identifier: "multiBlue")
blueLinePlot.lineColor = UIColor.colorFromHex(hexString: "#16aafc")
blueLinePlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic
// dots on the line
let blueDotPlot = DotPlot(identifier: "multiBlueDot")
blueDotPlot.dataPointType = ScrollableGraphViewDataPointType.circle
blueDotPlot.dataPointSize = 5
blueDotPlot.dataPointFillColor = UIColor.colorFromHex(hexString: "#16aafc")
blueDotPlot.adaptAnimationType = ScrollableGraphViewAnimationType.elastic
// Setup the reference lines.
let referenceLines = ReferenceLines()
referenceLines.referenceLineLabelFont = UIFont.boldSystemFont(ofSize: 8)
referenceLines.referenceLineColor = UIColor.white.withAlphaComponent(0.2)
referenceLines.referenceLineLabelColor = UIColor.white
referenceLines.relativePositions = [0, 0.2, 0.4, 0.6, 0.8, 1]
referenceLines.dataPointLabelColor = UIColor.white.withAlphaComponent(1)
// Setup the graph
graphView.backgroundFillColor = UIColor.colorFromHex(hexString: "#333333")
graphView.dataPointSpacing = 80
graphView.shouldAnimateOnStartup = true
graphView.shouldAdaptRange = true
graphView.shouldRangeAlwaysStartAtZero = true
// Add everything to the graph.
graphView.addReferenceLines(referenceLines: referenceLines)
graphView.addPlot(plot: blueLinePlot)
graphView.addPlot(plot: blueDotPlot)
return graphView
}
// Constraints and Helper Functions
// ################################
private func setupConstraints() {
self.graphView.translatesAutoresizingMaskIntoConstraints = false
graphConstraints.removeAll()
let topConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 0)
let rightConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.right, multiplier: 1, constant: 0)
let bottomConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.bottom, multiplier: 1, constant: 0)
let leftConstraint = NSLayoutConstraint(item: self.graphView, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 0)
graphConstraints.append(topConstraint)
graphConstraints.append(bottomConstraint)
graphConstraints.append(leftConstraint)
graphConstraints.append(rightConstraint)
self.view.addConstraints(graphConstraints)
}
// Adding and updating the graph switching label in the top right corner of the screen.
private func addLabel(withText text: String) {
label.removeFromSuperview()
label = createLabel(withText: text)
label.isUserInteractionEnabled = true
let rightConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.right, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.right, multiplier: 1, constant: -20)
let topConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 80)
let heightConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 40)
let widthConstraint = NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: label.frame.width * 1.5)
let tapGestureRecogniser = UITapGestureRecognizer(target: self, action: #selector(didTap))
label.addGestureRecognizer(tapGestureRecogniser)
self.view.insertSubview(label, aboveSubview: reloadLabel)
self.view.addConstraints([rightConstraint, topConstraint, heightConstraint, widthConstraint])
}
private func addReloadLabel(withText text: String) {
reloadLabel.removeFromSuperview()
reloadLabel = createLabel(withText: text)
reloadLabel.isUserInteractionEnabled = true
let leftConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.left, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.left, multiplier: 1, constant: 20)
let topConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: self.view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: 80)
let heightConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 40)
let widthConstraint = NSLayoutConstraint(item: reloadLabel, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: reloadLabel.frame.width * 1.5)
let tapGestureRecogniser = UITapGestureRecognizer(target: self, action: #selector(reloadDidTap))
reloadLabel.addGestureRecognizer(tapGestureRecogniser)
self.view.insertSubview(reloadLabel, aboveSubview: graphView)
self.view.addConstraints([leftConstraint, topConstraint, heightConstraint, widthConstraint])
}
private func createLabel(withText text: String) -> UILabel {
let label = UILabel()
label.backgroundColor = UIColor.black.withAlphaComponent(0.5)
label.text = text
label.textColor = UIColor.white
label.textAlignment = NSTextAlignment.center
label.font = UIFont.boldSystemFont(ofSize: 14)
label.layer.cornerRadius = 2
label.clipsToBounds = true
label.translatesAutoresizingMaskIntoConstraints = false
label.sizeToFit()
return label
}
// Button tap events
func didTap(_ gesture: UITapGestureRecognizer) {
currentGraphType.next()
self.view.removeConstraints(graphConstraints)
graphView.removeFromSuperview()
switch(currentGraphType) {
case .bar:
graphView = createBarGraph(self.view.frame)
addReloadLabel(withText: "RELOAD")
addLabel(withText: "BAR")
}
self.view.insertSubview(graphView, belowSubview: reloadLabel)
setupConstraints()
}
func reloadDidTap(_ gesture: UITapGestureRecognizer) {
// TODO: Currently changing the number of data items is not supported.
// It is only possible to change the the actual values of the data before reloading.
// numberOfDataItems = 30
// data for graphs with a single plot
barPlotData = self.generateRandomData(self.numberOfDataItems!, max: 100, shouldIncludeOutliers: false)
blueLinePlotData = self.generateRandomData(self.numberOfDataItems!, max: 50)
let now = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "LLLL"
let nameOfMonth = dateFormatter.string(from: now)
var xAxisLabels: [String] = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth);
xAxisLabels = self.generateSequentialLabels(self.numberOfDataItems!, text: nameOfMonth)
graphView.reload()
}
// Data Generation
private func generateRandomData(_ numberOfItems: Int, max: Double, shouldIncludeOutliers: Bool = true) -> [Double] {
var data = [Double]()
var counter = 1
for _ in 0 ..< numberOfItems
{
for Result in xml["WebAPiResponse","Result"]
{
let SLPercent = Result["SLPercent"].text!;
let Date = Result["DateCST"].text!;
let DateFromService = Int(Date.substring(to:Date.index(Date.startIndex, offsetBy: 2)))
if (counter == DateFromService!)
{
data.append(Double(SLPercent)!)
}
}
data.append(Double(counter))
counter = counter + 1;
}
return data
}
private func generateRandomData(_ numberOfItems: Int, variance: Double, from: Double) -> [Double] {
var data = [Double]()
for _ in 0 ..< numberOfItems {
let randomVariance = Double(arc4random()).truncatingRemainder(dividingBy: variance)
var randomNumber = from
if(arc4random() % 100 < 50) {
randomNumber += randomVariance
}
else {
randomNumber -= randomVariance
}
data.append(randomNumber)
}
return data
}
private func generateSequentialLabels(_ numberOfItems: Int, text: String) -> [String] {
var labels = [String]()
for i in 0 ..< numberOfItems {
labels.append("\(text) \(i+1)")
}
return labels
}
// The type of the current graph we are showing.
enum GraphType {
case bar
mutating func next() {
switch(self) {
case .bar:
self = GraphType.bar
}
}
}
override var prefersStatusBarHidden : Bool {
return true
}
}
Related
I have a UITableView in which I want to show a message when the dataSource is empty. I do this with the well-known method of setting the backgroundView using the following extension:
extension UITableView {
func setEmptyMessage(_ message: String, _ image: String) {
let emptyView: UIView = {
let emptyView = UIView(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: self.frame.size.height))
return emptyView
}()
let contentView: UIView = {
let contentView = UIView()
contentView.translatesAutoresizingMaskIntoConstraints = false
return contentView
}()
let messageLabel = UILabel()
let messageCommentStyle = NSMutableParagraphStyle()
messageCommentStyle.lineHeightMultiple = 1.2
let attributedString = NSMutableAttributedString(string: message)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: lightFeedUserNameFontColor, range: NSRange(location: 0, length: attributedString.length))
attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: messageCommentStyle, range: NSRange(location: 0, length: attributedString.length))
attributedString.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: normalFontSize), range: NSRange(location: 0, length: attributedString.length))
messageLabel.attributedText = attributedString
messageLabel.numberOfLines = 0
messageLabel.font = UIFont.systemFont(ofSize: normalFontSize)
messageLabel.textAlignment = .center
messageLabel.sizeToFit()
messageLabel.translatesAutoresizingMaskIntoConstraints = false
let errorImage: UIImageView = {
let errorImage = UIImageView()
errorImage.translatesAutoresizingMaskIntoConstraints = false
return errorImage
}()
self.backgroundView = emptyView
emptyView.addSubview(contentView)
contentView.addSubview(errorImage)
contentView.addSubview(messageLabel)
contentView.centerYAnchor.constraint(equalTo: emptyView.centerYAnchor).isActive = true
contentView.centerXAnchor.constraint(equalTo: emptyView.centerXAnchor).isActive = true
contentView.leadingAnchor.constraint(equalTo: emptyView.leadingAnchor, constant: normalSpacing * 3).isActive = true
contentView.trailingAnchor.constraint(equalTo: emptyView.trailingAnchor, constant: -(normalSpacing * 3)).isActive = true
contentView.topAnchor.constraint(equalTo: errorImage.topAnchor).isActive = true
contentView.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor).isActive = true
messageLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor).isActive = true
messageLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).isActive = true
messageLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
func restore() {
self.backgroundView = nil
}
}
And I set it like this:
if(tableData.isEmpty) {
self.tableView.setEmptyMessage("No results!", "none")
} else {
self.tableView.restore()
}
self.tableView.reloadData()
No big deal, we all have seen this and probably used it. And it works great. However, I now have a UIButton on the bottom placed in the tableFooterView. This button stays on top of the UITableView because it automatically positions itself right under the last cell, which is precisely what I want when there is data, but now the empty message is shown in the middle of the screen while the button is above it. How can I fix this so there is a sort of frame when the dataSource is empty?
To illustrate:
So now you are setting your tableView backgroundView to your emptyView. You can take a look on your dataSource and if it's empty - just return new cell for empty state with this message.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count > 0 ? dataSource.count : 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if dataSource.count.isEmpty 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "EmptyCell")
cell.textLabel.text = "No Results!"
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "SomeCell")
return cell
}
}
Write an extension
extension UITableView
{
func addErrorMessageLabel(noDataText:String = "No data available")->UILabel
{
let noDatalabel:UILabel!
noDatalabel=UILabel(frame: CGRect(x: self.frame.size.width/2-200, y: self.frame.size.height/2-50, width: 400, height: 100))
noDatalabel.textColor = textThemeColor
noDatalabel.text=noDataText
noDatalabel.numberOfLines=0
noDatalabel.textAlignment = .center
noDatalabel.isHidden=true
self.addSubview(noDatalabel)
self.alignCenterToSuperView(item: noDatalabel, horizentally: true, vertically: true,height: 100,width: 400)
return noDatalabel
}
func alignCenterToSuperView(item:UIView,horizentally:Bool,vertically:Bool , height:Int, width:Int)
{
if horizentally
{
item.translatesAutoresizingMaskIntoConstraints = false
let xConstraint = NSLayoutConstraint(item: item, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([xConstraint])
}
if vertically
{
item.translatesAutoresizingMaskIntoConstraints = false
let yConstraint = NSLayoutConstraint(item: item, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([yConstraint])
}
let Height = NSLayoutConstraint(item: item, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant:CGFloat(height))
item.addConstraint(Height)
let Width = NSLayoutConstraint(item: item, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant:CGFloat(width))
item.addConstraints([Width,Height])
}
}
In your ViewController create
var noDataLabel:UILabel!
In your ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
noDataLabel = tableView.addErrorMessageLabel()
noDataLabel.text = "No data found" // or your message
}
And final step
if(tableData.isEmpty) {
noDataLabel.isHideen = false
} else {
noDataLabel.isHideen = true
}
Can someone tell me what am I doing wrong?
I have a tableCell and inside it I have a stackView and inside it a UIWebview added programatically.
class ChatTableViewCell: UITableViewCell {
#IBOutlet var stackView: UIStackView!
func setupCell(message: ChatMessage) {
addHtmlToView(msg: "<p>This is <b>bold text</b></p>")
}
func addHtmlToView(msg: String) {
let webView = UIWebView()
webView.loadHTMLString("<html><body><div id='mainHtml'>" + msg + "</div></body></html>", baseURL: nil)
webView.backgroundColor = UIColor.clear
webView.isOpaque = false
webView.delegate = self
webView.scrollView.isScrollEnabled = false
webView.scrollView.bounces = false
webView.backgroundColor = .red
webView.scrollView.contentInset = UIEdgeInsets(top: -8, left: -8, bottom: -8, right: -8)
webView.translatesAutoresizingMaskIntoConstraints = false
self.stackView.addArrangedSubview(webView)
heightWebViewConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 10)
widthWebViewConstraint = NSLayoutConstraint(item: webView, attribute: NSLayoutConstraint.Attribute.width, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 1)
webView.addConstraint(heightWebViewConstraint)
webView.addConstraint(widthWebViewConstraint)
NSLayoutConstraint.activate([ heightWebViewConstraint, widthWebViewConstraint])
}
}
extension ChatTableViewCell: UIWebViewDelegate {
func webView(_ webView: UIWebView, shouldStartLoadWith request: URLRequest, navigationType: UIWebView.NavigationType) -> Bool {
if navigationType == UIWebView.NavigationType.linkClicked {
UIApplication.shared.openURL(request.url!)
return false
}
return true
}
func webViewDidFinishLoad(_ webView: UIWebView) {
webView.frame.size.height = 1.0
webView.sizeToFit()
let result = NumberFormatter().number(from: webView.stringByEvaluatingJavaScript(from: "document.getElementById('mainHtml').offsetHeight") ?? "0")
heightWebViewConstraint.constant = 200
}
}
After this the cell render like this
Next I will use the value from "result" but this doesn't work with an hardcoded value either
I think you have to Add Distribution property of Stack view as "Fill Equally"
At a loss for what I am doing wrong, here is my custom UIButton:
import UIKit
class IteratorChevronButton: UIButton {
required init() {
super.init(frame: .zero)
self.setBackgroundImage(UIImage(named: "icon-chevron-right"), for: .normal)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Usage of IteratorChevronButton in a UIView class:
var btnNext: IteratorChevronButton {
let btn = IteratorChevronButton()
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}
func doInit() {
self.addSubview(btnNext)
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .width, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128))
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .height, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128))
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0))
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 10))
}
I am getting the following error:
I tried to make btnNext lazy but I get the following error:
Here is the code for my custom UIView class:
import UIKit
import AVFoundation
import RealmSwift
enum PlayerError {
case unknownError
}
class Player: UIView {
let circularSliderVerticalPostionString:String = "75"
let circularSliderWidthString:String = "180"
let circularSliderHeightString = "180"
var circularSliderWidth:CGFloat!
var circularSliderHeight:CGFloat!
let uiImageIconClose = UIImage(named: "icon-close")
var movieDimension: CGSize = CGSize.zero
var imageGenerator: AVAssetImageGenerator!
var duration: CMTime = CMTimeMake(0, 30)
var avPlayerLayer: AVPlayerLayer?
var avPlayer: AVPlayer!
var startedDragging: Bool = false
var ready: Bool = false
var gForce: Double = 0.0
var isInDoublePlayer:Bool = false //used as a User Runtime Define Attribute in DoublePlayerViewController.xib
lazy var canvas: DrawingLayerView = {
let dv = DrawingLayerView()
return dv
}()
//Set this variable to swithch between normal playback and slow mo
var playSlowMo: Bool {
get {
return playerToolBar.playUsingTimer
}
set {
playerToolBar.playUsingTimer = newValue
}
}
//This when set the playback will resume after user stop dragging... I think its worth showing to
//some of the customers, if I were a player I would like it to be like this :)
var continuePlaybackWhenUserStopDragging: Bool {
get {
return playerToolBar.autoPlayWhenStopDragging
}
set {
playerToolBar.autoPlayWhenStopDragging = newValue
}
}
var playbackComlete: ((_ error: PlayerError?) -> Void)? = nil
lazy var controlBarSize: CGSize = {
return CGSize(width: self.bounds.width*3/4, height: 100)
}()
lazy var playerToolBar: PlayerToolBar = {[unowned self] in
let bar = PlayerToolBar(frame: CGRect.zero)
bar.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(bar)
return bar
}()
let controlsBar: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
lazy var closeButton: UIButton = {
let btn = ExtendedBoundsButton(type: .custom)
btn.translatesAutoresizingMaskIntoConstraints = false
btn.setImage(self.uiImageIconClose, for: UIControlState())
btn.setTitleColor(UIColor.blue, for: UIControlState())
btn.isHidden = true
self.addSubview(btn)
return btn
}()
lazy var progressLabel: UILabel = {
let label = UILabel()
label.textColor = UIColor.white
return label
}()
var btnNext: IteratorChevronButton {
let btn = IteratorChevronButton()
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}
lazy var chevronImageRight: UIImageView = {
let image = UIImage(named:"icon-chevron-right")!
let imageView = UIImageView(image: image)
imageView.contentMode = .scaleToFill
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
lazy var circularSlider: BWCircularSliderView = {
let cs = BWCircularSliderView()
cs.translatesAutoresizingMaskIntoConstraints = false
cs.frame.size.width = self.circularSliderWidth
cs.frame.size.height = self.circularSliderHeight
return cs
}()
var exporter: AVAssetExportSession? = nil
var autoPlay: Bool = false
var progressTimer: Timer?
var movieDidPlay: (()->Void?)? = nil
var onTap: (()-> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
doInit()
}
func doInit() {
self.circularSliderWidth = CGFloat(Int(circularSliderWidthString)!)
self.circularSliderHeight = CGFloat(Int(circularSliderHeightString)!)
self.addSubview(chevronImageRight)
self.addSubview(progressLabel)
self.addSubview(circularSlider)
self.addSubview(btnNext)
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .width, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128))
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .height, relatedBy: .equal,
toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128))
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1.0, constant: 0)
)
self.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1.0, constant: 10))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-\(circularSliderVerticalPostionString)-[circularSlider(\(circularSliderWidthString))]", options: [], metrics: nil, views: ["circularSlider": circularSlider]))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[circularSlider(\(circularSliderHeightString))]|", options: [], metrics: nil, views: ["circularSlider": circularSlider]))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[toolbar(100)]-0-|", options: [], metrics: nil, views: ["toolbar": playerToolBar]))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[toolbar]|", options: [], metrics: nil, views: ["toolbar": playerToolBar]))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-35-[btn(40)]", options: [], metrics: nil, views: ["btn": closeButton]))
self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:[btn(40)]-10-|", options: [], metrics: nil, views: ["btn": closeButton]))
closeButton.addTarget(self, action: #selector(onClose), for: .touchUpInside)
}
func onClose() {
if !ready {
return
}
if let periodicTimeObserver = playerToolBar.periodicTimeObserver {
self.avPlayer.removeTimeObserver(periodicTimeObserver)
}
self.avPlayer.pause()
progressTimer?.invalidate()
playbackComlete?(nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
doInit()
}
override func layoutSubviews() {
super.layoutSubviews()
playerToolBar.isInDoublePlayer = self.isInDoublePlayer
self.circularSlider.isHidden = self.isInDoublePlayer
self.circularSlider.gForce = self.gForce
if let avPlayerLayer = avPlayerLayer {
avPlayerLayer.bounds = self.bounds
avPlayerLayer.position = CGPoint(x: self.bounds.width/2, y: self.bounds.height/2)
playerToolBar.avPlayer = avPlayer
playerToolBar.setupMovieScrollBar()
if autoPlay {
autoPlay = false
play()
}
movieDidPlay?()
}
progressLabel.frame = CGRect(x: frame.size.width/2-100, y: frame.size.height/2-15, width: 200, height: 30)
addSubview(canvas)
addSubview(playerToolBar)
addSubview(closeButton)
canvas.frame = bounds
}
func onExportTimer(_ sender: AnyObject) {
guard let exporter = exporter else {
return
}
progressLabel.text = "Processing " + String(Int(exporter.progress*100) ) + "%"
}
func mergeFiles(_ items: [String], assetWithOnset: String?, mergeComplete: #escaping (_ fileName: String?)->Void) -> Void {
if (assetWithOnset == nil) {
mergeComplete(items.first!)
return
}
let composition = AVMutableComposition()
let track:AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID())
var insertTime = kCMTimeZero
for item in items {
let sourceAsset = AVAsset(url: URL(fileURLWithPath: FileUtility.getPathForFileMovieDirectory(item)))
let tracks = sourceAsset.tracks(withMediaType: AVMediaTypeVideo)
print("\(item) \(sourceAsset.isPlayable)") // print true
print(sourceAsset.isExportable) // print true
print(sourceAsset.isReadable) // print true
if tracks.count > 0 {
let assetTrack:AVAssetTrack = tracks[0] as AVAssetTrack
do {
try track.insertTimeRange(CMTimeRangeMake(kCMTimeZero,sourceAsset.duration), of: assetTrack, at: insertTime)
insertTime = CMTimeAdd(insertTime, sourceAsset.duration)
} catch {
mergeComplete(nil)
return
}
}
}
let fusedFileName = "fused_" + assetWithOnset!
let fusedFilePath = FileUtility.getPathForFileMovieDirectory(fusedFileName)
let fusedFileUrl = URL(fileURLWithPath: fusedFilePath)
do {
//in case the file merging fails, the residual file will cause
//the file export fail everytime as the file exist
try FileManager.default.removeItem(atPath: fusedFilePath)
} catch {
}
exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPreset1280x720)
guard let exporter = exporter else {
return
}
exporter.outputURL = fusedFileUrl
exporter.outputFileType = AVFileTypeQuickTimeMovie
progressTimer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(onExportTimer(_:)), userInfo: nil, repeats: true);
exporter.exportAsynchronously(completionHandler: {
switch exporter.status{
case AVAssetExportSessionStatus.failed:
if exporter.error != nil {
print("AVAssetExportSession failed \(exporter.error!)")
}else{
print("AVAssetExportSession failed for unknown reason")
}
mergeComplete(nil)
case AVAssetExportSessionStatus.cancelled:
if exporter.error != nil {
print("AVAssetExportSession canceled \(exporter.error!)")
}else{
print("AVAssetExportSession canceled for unknown reason")
}
mergeComplete(nil)
default:
do {
let realm = try Realm()
let movieClip = realm.object(ofType: MovieModel.self, forPrimaryKey: assetWithOnset)
try realm.write {
movieClip?.fusedFile = fusedFileName
}
//The files are released based on the usage count
MovieRepository.sharedInstance.release(file: movieClip?.fileName)
MovieRepository.sharedInstance.release(file: movieClip?.nextFile)
MovieRepository.sharedInstance.release(file: movieClip?.prevFile)
} catch {
}
mergeComplete(fusedFileName)
self.progressLabel.text = ""
self.progressLabel.isHidden = true
NotificationUtility.notifyReloadGallery()
}
})
}
func setMovies(_ items: [String], itemWithOnset asset: String?, playbackCompletion completion: #escaping ((_ error: PlayerError?) -> Void)){
playbackComlete = completion
closeButton.isHidden = false
mergeFiles(items, assetWithOnset: asset ) { [weak self] (fileName) in
DispatchQueue.main.async(execute: { () -> Void in
if let fileName = fileName, let strongSelf = self {
let asset = AVAsset(url: URL(fileURLWithPath: FileUtility.getPathForFileMovieDirectory(fileName)))
let avplayerItem = AVPlayerItem(asset: asset)
strongSelf.progressTimer?.invalidate()
strongSelf.progressLabel.removeFromSuperview()
strongSelf.duration = asset.duration
strongSelf.avPlayer = AVPlayer(playerItem: avplayerItem)
if let playerLayer = strongSelf.avPlayerLayer {
playerLayer.removeFromSuperlayer()
}
strongSelf.avPlayerLayer = AVPlayerLayer(player: strongSelf.avPlayer)
strongSelf.avPlayerLayer?.zPosition = -1 //send to back
strongSelf.self.layer.addSublayer(strongSelf.avPlayerLayer!)
NotificationCenter.default.addObserver(strongSelf, selector: #selector(Player.currentFileDidFinish(_:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: avplayerItem)
print("Duration \(Float(CMTimeGetSeconds(strongSelf.duration)))")
print("Size \(strongSelf.movieDimension)")
strongSelf.ready = true
strongSelf.autoPlay = true;
strongSelf.setNeedsLayout()
}
})
}
}
func setMovie(movieAsset: MovieModel, completion: #escaping ()->Void) {
movieDidPlay = completion
autoPlay = true
playerToolBar.playeBackTimer?.invalidate()
playerToolBar.playeBackTimer = nil
var clipNames: [String]
var assetWithOnset: String? = nil
if let fusedFile = movieAsset.fusedFile {
clipNames = [fusedFile]
} else {
assetWithOnset = movieAsset.fileName
if let nextFile = movieAsset.nextFile {
clipNames = [movieAsset.prevFile!, movieAsset.fileName!, nextFile]
} else {
clipNames = [movieAsset.prevFile!, movieAsset.fileName!]
}
}
self.setMovies(clipNames, itemWithOnset: assetWithOnset, playbackCompletion: { (err) in
})
closeButton.isHidden = true
}
func resolutionSizeForVideo(_ asset:AVAsset) -> CGSize? {
guard let track = asset.tracks(withMediaType: AVMediaTypeVideo).first else { return nil }
let size = track.naturalSize.applying(track.preferredTransform)
return CGSize(width: fabs(size.width), height: fabs(size.height))
}
//MARK: The playback methods
func pause(){
if ready {
playerToolBar.pause()
}
}
func play() {
if ready {
playerToolBar.play()
}
}
func currentFileDidFinish(_ notification: Notification) {
/* if let periodicTimeObserver = playerToolBar.periodicTimeObserver {
self.avPlayer.removeTimeObserver(periodicTimeObserver)
}
progressTimer?.invalidate()
playbackComlete?(error: nil)*/
avPlayer.seek(to: CMTimeMake(0, 30))
avPlayer.rate = 1.0
}
func stop() {
avPlayer?.pause()
avPlayer = nil
avPlayerLayer?.removeFromSuperlayer()
avPlayerLayer = nil
}
deinit {
NotificationCenter.default.removeObserver(self)
}
//MARK:
}
class CollectionViewThumbNailCell: UICollectionViewCell {
lazy var barView: UIView = {
let lbl = UIView()
lbl.contentMode = .scaleToFill
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.backgroundColor = UIColor.white
lbl.layer.cornerRadius = 2
lbl.clipsToBounds = true
return lbl
}()
override init(frame: CGRect) {
super.init(frame: frame)
contentView.addSubview(barView)
}
func configureMark(_ big: Bool) {
if big {
barView.frame = CGRect(x: bounds.size.width/2 - 2, y: 2, width: 4, height: bounds.size.height)
} else {
barView.frame = CGRect(x: bounds.size.width/2 - 2, y: bounds.size.height/2+2, width: 4, height: bounds.size.height/2)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I guess error is due to this (your btnNext is not being made properly):
var btnNext: IteratorChevronButton {
let btn = IteratorChevronButton()
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}
use this syntax to make btnNext
var btnNext: IteratorChevronButton = {
let btn = IteratorChevronButton()
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
You instantiate a new button every time you access btnNext. So after adding one instance as a subview you use other instances for creating the constraints. Since those other subviews are no subviews of self the app crashes.
Make it a lazy var to instantiate it only once:
lazy var btnNext: IteratorChevronButton = {
let btn = IteratorChevronButton()
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
You also have to add the width and height constraint to the button itself instead of self:
btnNext.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128))
btnNext.addConstraint(NSLayoutConstraint(item: btnNext, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: 128))
Update:
As Ahmad F stated the lazy keyword is totally optional in this case. You could simply instantiate your button without it if you will definitely use it:
var btnNext: IteratorChevronButton = {
let btn = IteratorChevronButton()
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
I'm trying to animate UITableView to act like a dropdownMenu by using its height constraint and UIView.animateWithDamping(..) block. I'm occuring weird problem with white background under tableView.
iPhone Simulator showing the problem
I have cleared each background color and it doesn't help much.
Here is the code setting all subviews of dropDownView, which is a UIView:
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.elements = []
defaultSetup()
}
private func defaultSetup() {
configureActionButton()
configureTableView()
}
private func configureActionButton() {
actionButton = UIButton(frame: CGRectZero)
actionButton.translatesAutoresizingMaskIntoConstraints = false
addSubview(actionButton)
guard let superview = actionButton.superview else {
assert(false, "ActionButton adding to superview failed.")
return
}
// Constraints
actionButton.constrain(.Leading, .Equal, superview, .Leading, constant: 0, multiplier: 1)?.constrain(.Trailing, .Equal, superview, .Trailing, constant: 0, multiplier: 1)?.constrain(.Top, .Equal, superview, .Top, constant: 0, multiplier: 1)?.constrain(.Bottom, .Equal, superview, .Bottom, constant: 0, multiplier: 1)
// Appearance
actionButton.backgroundColor = UIColor.clearColor()
actionButton.opaque = false
actionButton.contentHorizontalAlignment = .Left
actionButton.contentEdgeInsets = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 0)
if borderVisible {
actionButton.layer.cornerRadius = 5
actionButton.layer.borderColor = UIColor.blackColor().CGColor
actionButton.layer.borderWidth = 1
actionButton.clipsToBounds = true
}
// Actions
actionButton.addTarget(self, action: "menuAction:", forControlEvents: .TouchUpInside)
}
private func configureTableView() {
tableView = BOTableView(frame: CGRectZero, items: elements, configuration: configuration)
tableView.translatesAutoresizingMaskIntoConstraints = false
tableView.delegate = self
tableView.dataSource = self
addSubview(tableView)
guard let tableViewSuperview = tableView.superview else {
assert(false, "TableView adding to superview failed.")
return
}
// Constraints
tableView.constrain(.Trailing, .Equal, tableViewSuperview, .Trailing, constant: 0, multiplier: 1)?.constrain(.Top, .Equal, tableViewSuperview, .Bottom, constant: 0, multiplier: 1)?.constrain(.Leading, .Equal, tableViewSuperview, .Leading, constant: 0, multiplier: 1)
tvHeightConstraint = NSLayoutConstraint(item: tableView, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)
tableView.addConstraint(tvHeightConstraint)
}
BOTableView class initializer:
init(frame: CGRect, items: [String], configuration: BOConfiguration) {
super.init(frame: frame, style: UITableViewStyle.Plain)
self.items = items
self.selectedIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.configuration = configuration
// Setup table view
self.opaque = false
self.backgroundView?.backgroundColor = UIColor.clearColor()
self.backgroundColor = UIColor.clearColor()
self.separatorColor = UIColor.blackColor()
self.scrollEnabled = false
self.separatorStyle = .SingleLine
self.layer.cornerRadius = 5
self.layer.borderColor = UIColor.blackColor().CGColor
self.layer.borderWidth = 1
self.clipsToBounds = true
}
UIView animations:
private func showMenuWithCompletionBlock(completion: (succeeded: Bool) -> Void) {
delegate?.menuWillShow(self)
let tvHeight = frame.size.height * CGFloat(elements.count)
tvHeightConstraint.constant = tvHeight
UIView.animateWithDuration(0.5, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.5, options: .CurveEaseInOut, animations: { [weak self] () -> Void in
guard let strongSelf = self else {
completion(succeeded: false)
return
}
strongSelf.layoutIfNeeded()
}, completion: { (finished) -> Void in
if finished {
completion(succeeded: true)
}
})
}
Here is the code for UIView + Constraints extension, used in code:
extension UIView {
/**
:returns: true if v is in this view's super view chain
*/
public func isSuper(v : UIView) -> Bool
{
for var s : UIView? = self; s != nil; s = s?.superview {
if(v == s) {
return true;
}
}
return false
}
public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, _ otherView: UIView, _ otherAttribute: NSLayoutAttribute, constant: CGFloat = 0.0, multiplier : CGFloat = 1.0) -> UIView?
{
let c = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: otherView, attribute: otherAttribute, multiplier: multiplier, constant: constant)
if isSuper(otherView) {
otherView.addConstraint(c)
return self
}
else if(otherView.isSuper(self) || otherView == self)
{
self.addConstraint(c)
return self
}
assert(false)
return nil
}
public func constrain(attribute: NSLayoutAttribute, _ relation: NSLayoutRelation, constant: CGFloat, multiplier : CGFloat = 1.0) -> UIView?
{
let c = NSLayoutConstraint(item: self, attribute: attribute, relatedBy: relation, toItem: nil, attribute: .NotAnAttribute, multiplier: multiplier, constant: constant)
self.addConstraint(c)
return self
}
}
When I tried to debug the views' hierarchy in debugger, the only view which had white background was tableView, but I have cleared the background in code. I have also tried to set tableView's backgroundView to nil as well as backgroundView.backgroundColor to clearColor(). Nothing changed.
Maybe try to set the UITableView footer to a blank view, don't really know why, but it seams to help for similar issue like You have.
[_tableView setTableFooterView:[[UIView alloc] init]];
I have a fraction calculator and when I calculate it, it turns into a decimal. I want to turn it into a fraction. I don't know how to do it in code but I tried separating the calculations but it wouldn't work here is the code with a screenshot of my storyboard https://www.dropbox.com/s/fmapxaou49zap05/iOS%20Simulator%20Screen%20Shot%20Oct%2015%2C%202014%2C%209.37.43%20PM.png?dl=0
//Adding Function
changingSignsLabel.text = "+"
let firstDenomInTextField:Double! = (firstDenominatorTextField.text as NSString).doubleValue
let firstNumInTextField:Double! = (firstNumeratorTextField.text as NSString).doubleValue
let firstWholeInTextField:Double! = (firstWholeNumberTextField.text as NSString).doubleValue
let secondDenomInTextField:Double! = (secondDenominatorTextField.text as NSString).doubleValue
let secondNumInTextField:Double! = (secondNumeratorTextField.text as NSString).doubleValue
let secondWholeInTextField:Double! = (secondWholeNumberTextField.text as NSString).doubleValue
var firstStep = firstDenomInTextField! * firstWholeInTextField! / firstDenomInTextField!
var secondStep = firstStep + firstNumInTextField! / firstDenomInTextField!
var thirdStep = secondDenomInTextField! * secondWholeInTextField! / secondDenomInTextField!
var fourthStep = thirdStep + secondNumInTextField! / secondDenomInTextField!
var calculatedAnswer = (secondStep + fourthStep)
answerLabel.hidden = false
answerLabel.text = "\(calculatedAnswer)"
If you have any questions or concerns please comment them down below
Thank you for your help
For this to work every time, you want to use the Euclidian Algorithm, which in swift looks like this:
func simplify(top:Int, bottom:Int) -> (newTop:Int, newBottom:Int) {
var x = top
var y = bottom
while (y != 0) {
var buffer = y
y = x % y
x = buffer
}
var hcfVal = x
var newTopVal = top/hcfVal
var newBottomVal = bottom/hcfVal
return(newTopVal, newBottomVal)
}
You should just be able to add this to your program, then call it with:
simplify(5,10)
...returning 1 and 2. Hope this helps anyone else trying to find a simpler answer to this question.
Updated Answer
I wrote you a demo app:
Swift Source Code:
Put this inside your view controller.
var numeratorField:UITextField?;
var denominatorField:UITextField?;
var equalButton:UIButton?;
var wholeNumberLabel:UILabel?;
var numeratorLabel:UILabel?;
var denominatorLabel:UILabel?;
var dividerLine:UIView?;
var appName:UILabel?;
var instructions:UILabel?;
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
initViews();
initConstraints();
}
// ---------------------------------------------------------------------
// MARK: Interface Setup
// ---------------------------------------------------------------------
func initViews()
{
self.numeratorField = UITextField();
self.numeratorField?.layer.cornerRadius = 5.0;
self.numeratorField?.layer.borderWidth = 1.0;
self.numeratorField?.textAlignment = NSTextAlignment.Center;
self.denominatorField = UITextField();
self.denominatorField?.layer.cornerRadius = 5.0;
self.denominatorField?.layer.borderWidth = 1.0;
self.denominatorField?.textAlignment = NSTextAlignment.Center;
self.equalButton = UIButton();
self.equalButton?.setTitle("=", forState: UIControlState.Normal);
self.equalButton?.addTarget(self, action: "calculateAnswer", forControlEvents: UIControlEvents.TouchUpInside);
self.equalButton?.setTitleColor(UIColor.darkGrayColor(), forState: UIControlState.Normal);
self.equalButton?.layer.cornerRadius = 5.0;
self.equalButton?.backgroundColor = UIColor.lightGrayColor();
self.wholeNumberLabel = UILabel();
self.wholeNumberLabel?.textAlignment = NSTextAlignment.Right;
self.wholeNumberLabel?.font = UIFont(name: "HelveticaNeue", size: 20);
self.numeratorLabel = UILabel();
self.numeratorLabel?.textAlignment = NSTextAlignment.Center;
self.numeratorLabel?.font = UIFont(name: "HelveticaNeue", size: 14);
self.denominatorLabel = UILabel();
self.denominatorLabel?.textAlignment = NSTextAlignment.Center;
self.denominatorLabel?.font = UIFont(name: "HelveticaNeue", size: 14);
self.dividerLine = UIView();
self.dividerLine?.backgroundColor = self.numeratorLabel!.textColor;
self.dividerLine?.alpha = 0;
self.appName = UILabel();
self.appName?.text = "Fraction App";
self.appName?.font = UIFont(name: "HelveticaNeue-Bold", size: 24);
self.appName?.textAlignment = NSTextAlignment.Center;
self.instructions = UILabel();
self.instructions?.textAlignment = NSTextAlignment.Center;
self.instructions?.text = "Enter a value in the numerator and denominator field, then press the equal sign to see the answer";
self.instructions?.numberOfLines = 0;
self.instructions?.lineBreakMode = NSLineBreakMode.ByWordWrapping;
self.view.addSubview(self.numeratorField!);
self.view.addSubview(self.denominatorField!);
self.view.addSubview(self.equalButton!);
self.view.addSubview(self.wholeNumberLabel!);
self.view.addSubview(self.numeratorLabel!);
self.view.addSubview(self.denominatorLabel!);
self.view.addSubview(self.dividerLine!);
self.view.addSubview(self.appName!);
self.view.addSubview(self.instructions!);
}
func initConstraints()
{
self.appName?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.numeratorField?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.denominatorField?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.equalButton?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.wholeNumberLabel?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.numeratorLabel?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.denominatorLabel?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.dividerLine?.setTranslatesAutoresizingMaskIntoConstraints(false);
self.instructions?.setTranslatesAutoresizingMaskIntoConstraints(false);
var views:NSMutableDictionary = NSMutableDictionary();
views.setValue(self.appName, forKey: "appName");
views.setValue(self.numeratorField, forKey: "numeratorField");
views.setValue(self.denominatorField, forKey: "denominatorField");
views.setValue(self.equalButton, forKey: "equalButton");
views.setValue(self.wholeNumberLabel, forKey: "wholeNumberLabel")
views.setValue(self.numeratorLabel, forKey: "numeratorLabel");
views.setValue(self.denominatorLabel, forKey: "denominatorLabel");
views.setValue(self.dividerLine, forKey: "dividerLine");
views.setValue(self.instructions, forKey: "instructions");
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[appName]|", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-50-[appName]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-20-[instructions]-20-|", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[instructions]-100-|", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-35-[numeratorField(75)]-10-[denominatorField(==numeratorField)]-10-[equalButton]-10-[wholeNumberLabel(>=20)]-2-[dividerLine(15)]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[numeratorField(50)]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[denominatorField(==numeratorField)]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[equalButton(==numeratorField)]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[wholeNumberLabel(==numeratorField)]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[numeratorLabel]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[denominatorLabel]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[dividerLine(1)]", options: nil, metrics: nil, views: views));
self.view.addConstraint(NSLayoutConstraint(item: self.numeratorLabel!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.wholeNumberLabel, attribute: NSLayoutAttribute.Right, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.denominatorLabel!, attribute: NSLayoutAttribute.Left, relatedBy: NSLayoutRelation.Equal, toItem: self.wholeNumberLabel, attribute: NSLayoutAttribute.Right, multiplier: 1.0, constant: 0.0));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[numeratorLabel(==wholeNumberLabel)]", options: nil, metrics: nil, views: views));
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[denominatorLabel(==wholeNumberLabel)]", options: nil, metrics: nil, views: views));
self.view.addConstraint(NSLayoutConstraint(item: self.numeratorField!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.denominatorField!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.equalButton!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.wholeNumberLabel!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.dividerLine!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.numeratorLabel!, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.wholeNumberLabel, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
self.view.addConstraint(NSLayoutConstraint(item: self.denominatorLabel!, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.wholeNumberLabel, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0));
}
func getLowestCommonDenominator(Numerator numerator:Int, Denominator denominator:Int) -> Int
{
var finalDenominator = denominator;
//println("\(numerator) / \(finalDenominator) = \(numerator % finalDenominator)");
if(numerator % 2 == 0 && finalDenominator % 2 == 0) // even number
{
return self.getLowestCommonDenominator(Numerator: numerator / 2.0, Denominator: finalDenominator / 2.0);
}
else if(numerator % 3 == 0 && finalDenominator % 3 == 0) // odd number
{
return self.getLowestCommonDenominator(Numerator: numerator / 3.0, Denominator: finalDenominator / 3.0);
}
else // prime number, always have a remainder
{
//println("lowest common denominator = \(finalDenominator)");
return finalDenominator;
}
}
func calculateAnswer()
{
// prevent division by zero
if(self.denominatorField?.text == "0")
{
var alert:UIAlertView = UIAlertView(title: "Division By Zero", message: "Divide by zero yields undefined value", delegate: self, cancelButtonTitle: "OK");
alert.show();
return;
}
// clear previous values;
self.wholeNumberLabel?.text = "";
self.numeratorLabel?.text = "";
self.denominatorLabel?.text = "";
var numeratorString:NSString = self.numeratorField!.text;
var denominatorString:NSString = self.denominatorField!.text;
if (numeratorString.length == 0 || denominatorString.length == 0)
{
//var alert:UIAlertView = UIAlertView(title: "Input Missing", message: "Enter a value for numerator and denominator then press the equal sign to get the answer", delegate: nil, cancelButtonTitle: "OK", otherButtonTitles: nil, nil);
var alert:UIAlertView = UIAlertView(title: "Input Missing", message: "Enter a value for numerator and denominator then press the equal sign to get the answer", delegate: nil, cancelButtonTitle: "OK");
alert.show();
}
else
{
var shouldShowWholeParts:Bool = false;
var shouldShowFractionParts:Bool = false;
var numerator:Int = self.numeratorField!.text.toInt()!;
var denominator:Int = self.denominatorField!.text.toInt()!;
var finalNumerator = numerator;
var finalDenominator = self.getLowestCommonDenominator(Numerator: numerator, Denominator: denominator);
if(finalDenominator < denominator)
{
denominator = finalDenominator;
numerator = numerator / finalDenominator;
}
var wholeNumbers:Int = numerator / denominator;
var remainder:Int = numerator % denominator;
//println("wholeNumbers = \(wholeNumbers), remainder = \(remainder)");
//println("\(denominator) % \(remainder) = \(denominator % remainder)");
if(wholeNumbers != 0)
{
shouldShowWholeParts = true;
}
else
{
shouldShowWholeParts = false;
}
if(remainder > 0)
{
// see if we can simply the fraction part as well
if(denominator % remainder == 0) // no remainder means remainder can be simplified further
{
finalDenominator = denominator / remainder;
finalNumerator = remainder / remainder;
}
else
{
finalNumerator = remainder;
finalDenominator = denominator;
}
shouldShowFractionParts = true;
}
else
{
shouldShowFractionParts = false;
}
var answer:NSString?;
if(wholeNumbers > 0 && remainder > 0)
{
answer = NSString(format: "\(wholeNumbers) \(finalNumerator)/\(finalDenominator)");
// prints out whole number and fraction parts
//println("Simplified fraction of \(numerator)/\(denominator) = \(wholeNumbers) \(finalNumerator)/\(finalDenominator)");
}
else if (wholeNumbers > 0 && remainder == 0)
{
answer = NSString(format: "\(wholeNumbers)");
// prints out whole number only
//println("Simplified fraction of \(numerator)/\(denominator) = \(wholeNumbers)");
}
else
{
answer = NSString(format: "\(finalNumerator)/\(finalDenominator)");
// prints out fraction part only
//println("Simplified fraction of \(numerator)/\(denominator) = \(finalNumerator)/\(finalDenominator)");
}
if(shouldShowWholeParts)
{
self.wholeNumberLabel?.text = NSString(format: "\(wholeNumbers)");
}
if(shouldShowFractionParts)
{
self.numeratorLabel?.text = NSString(format: "\(finalNumerator)");
self.denominatorLabel?.text = NSString(format: "\(finalDenominator)");
self.dividerLine?.alpha = 1;
}
else
{
self.dividerLine?.alpha = 0;
}
}
}
Original Answer
Is this what you're looking for?
Results:
Simplified fraction of 7/8 = 7/8
Simplified fraction of 12/8 = 1 1/2
Simplified fraction of 5/16 = 5/16
Simplified fraction of 342/23 = 14 20/23
Simplified fraction of 129/72 = 1 57/72
Source Code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
printSimplifiedFraction(Numerator: 7, Denominator: 8);
printSimplifiedFraction(Numerator: 12, Denominator: 8);
printSimplifiedFraction(Numerator: 5, Denominator: 16);
printSimplifiedFraction(Numerator: 342, Denominator: 23);
printSimplifiedFraction(Numerator: 129, Denominator: 72);
}
func printSimplifiedFraction(Numerator numerator: Int, Denominator denominator:Int)
{
var finalNumerator = numerator;
var finalDenominator = denominator;
var wholeNumbers:Int = numerator / denominator;
var remainder:Int = numerator % denominator;
//println("wholeNumbers = \(wholeNumbers), remainder = \(remainder)");
//println("\(denominator) % \(remainder) = \(denominator % remainder)");
if(remainder > 0)
{
// see if we can simply the fraction part as well
if(denominator % remainder == 0) // no remainder means remainder can be simplified further
{
finalDenominator = denominator / remainder;
finalNumerator = remainder / remainder;
}
else
{
finalNumerator = remainder;
finalDenominator = denominator;
}
}
if(wholeNumbers > 0 && remainder > 0)
{
// prints out whole number and fraction parts
println("Simplified fraction of \(numerator)/\(denominator) = \(wholeNumbers) \(finalNumerator)/\(finalDenominator)");
}
else if (wholeNumbers > 0 && remainder == 0)
{
// prints out whole number only
println("Simplified fraction of \(numerator)/\(denominator) = \(wholeNumbers)");
}
else
{
// prints out fraction part only
println("Simplified fraction of \(numerator)/\(denominator) = \(finalNumerator)/\(finalDenominator)");
}
}
Unfortunately there is not framework you can import to display a fraction in swift or Xcode. What I would recommend is using the same method you used in your storyboard: Have two UILabelView's, one on top of each other, with a line between them.
Instead of calculating the decimal, use basic math to find the numerator and denominator and display them on the top and bottom of the UILabelView.
This method will require changing your program at a fundamental level, however it will work.