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
}()
Related
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
}
}
I keep trying to implement input accessory VC in my app and I faced with the following issue. When I'm trying to modify the height of the root view of my custom UIInputViewController it's working well despite the one problem. The problem is that in the logs I see the following:
[LayoutConstraints] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
<NSAutoresizingMaskLayoutConstraint:0x174490cc0 UIInputView:0x10a80bb80.(null) == 46.5>,
<NSAutoresizingMaskLayoutConstraint:0x17448cd50 UIInputView:0x10a80bb80.height == 76>,
<NSLayoutConstraint:0x17429b710 UIInputView:0x10a80bb80.top == UIInputSetHostView:0x10402e940.top>
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x17429b710 UIInputView:0x10a80bb80.top == UIInputSetHostView:0x10402e940.top>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
Code of my custom UIInputViewController:
import UIKit
import RxSwift
import RxCocoa
#objc protocol InputViewControllerDelegate: NSObjectProtocol {
func answerTextViewDidChange(_ textView: UITextView)
}
class InputViewController: UIInputViewController {
fileprivate var closeButton: UIButton?
private var separatorView: UIView?
private var tipLabel: UILabel?
private var answerTextView: ConstrainedTextView?
private var buttonHeightConstraint: NSLayoutConstraint?
private var separatorHeightConstraint: NSLayoutConstraint?
private var answerTextViewBottomConstraint: NSLayoutConstraint?
weak var delegate: InputViewControllerDelegate?
private let junk = DisposeBag()
private var appropriateMaxLines: Int {
let isPortrait = UIDevice.current.orientation.isPortrait
return isPortrait ? 5 : 3
}
var answerText: String {
get {
return answerTextView?.text ?? ""
}
set {
answerTextView?.text = newValue
}
}
var isAnswerTextViewFirstResponder: Bool {
get {
return answerTextView?.isFirstResponder ?? false
}
set {
_ = newValue ? answerTextView?.becomeFirstResponder() : answerTextView?.resignFirstResponder()
}
}
// MARK: - Life Cycle
deinit {
NotificationCenter.default.removeObserver(self)
}
override func loadView() {
super.loadView()
let notifName = Notification.Name.UIDeviceOrientationDidChange
NotificationCenter.default.addObserver(self,
selector: #selector(rotated),
name: notifName,
object: nil)
configureView()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
answerTextView?.maxLines = appropriateMaxLines
setCloseButtonDisabledIfNeeded()
}
// MARK: - Layout
private func configureView() {
view.backgroundColor = RGB(0xF6F6F6)
view.frame = CGRect(x: 0, y: 0, width: screenWidth, height: 70)
view.autoresizingMask = [.flexibleWidth]
// Separator
separatorView = UIView()
separatorView?.backgroundColor = UIColor.lightGray
separatorView?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(separatorView!)
AutoLayoutEqualizeSuper(separatorView, .left, 0)
AutoLayoutEqualizeSuper(separatorView, .right, 0)
AutoLayoutEqualizeSuper(separatorView, .top, 0)
separatorHeightConstraint = AutoLayoutSetAttribute(separatorView, .height, 1)
// Close Button
closeButton = UIButton(type: .system)
closeButton?.setTitle("Hide", for: .normal)
closeButton?.titleLabel?.font = UIFont.systemFont(ofSize: 17)
closeButton?.translatesAutoresizingMaskIntoConstraints = false
closeButton?.addTarget(self, action: #selector(dismissKeyboard), for: .touchUpInside)
view.addSubview(closeButton!)
AutoLayoutSetAttribute(closeButton, .width, 70)
buttonHeightConstraint = AutoLayoutSetAttribute(closeButton, .height, 35)
AutoLayoutEqualizeSuper(closeButton, .right, -5)
view.addConstraint(NSLayoutConstraint(item: closeButton!, attribute: .top, relatedBy: .equal, toItem: separatorView, attribute: .bottom, multiplier: 1, constant: 0))
// Tip Label
tipLabel = UILabel()
tipLabel?.textColor = UIColor.darkGray
tipLabel?.text = "Your answer:"
tipLabel?.font = UIFont.systemFont(ofSize: 17)
tipLabel?.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(tipLabel!)
AutoLayoutEqualizeSuper(tipLabel, .left, 5)
AutoLayoutSetAttribute(tipLabel, .height, 35)
view.addConstraint(NSLayoutConstraint(item: tipLabel!, attribute: .right, relatedBy: .equal, toItem: closeButton, attribute: .left, multiplier: 1, constant: 0))
// Text View
answerTextView = ConstrainedTextView()
answerTextView?.backgroundColor = UIColor.white
answerTextView?.delegate = self
answerTextView?.scrollsToTop = false
answerTextView?.showsVerticalScrollIndicator = false
answerTextView?.font = REG_FONT(15)
answerTextView?.translatesAutoresizingMaskIntoConstraints = false
answerTextView?.layer.masksToBounds = true
answerTextView?.layer.cornerRadius = 7
answerTextView?.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.7).cgColor
answerTextView?.layer.borderWidth = 1
view.addSubview(answerTextView!)
AutoLayoutEqualizeSuper(answerTextView, .left, 5)
AutoLayoutEqualizeSuper(answerTextView, .right, -5)
answerTextViewBottomConstraint = AutoLayoutEqualizeSuper(answerTextView, .bottom, -5)
answerTextView?
.rx
.observe(CGRect.self, "bounds")
.distinctUntilChanged {
$0?.height == $1?.height
}
.subscribe(onNext: { [unowned self] newBounds in
if var newHeight = newBounds?.height,
let separatorHeight = self.separatorHeightConstraint?.constant,
let buttonHeight = self.buttonHeightConstraint?.constant,
let bottomSpace = self.answerTextViewBottomConstraint?.constant {
newHeight = newHeight < 35 ? 35 : newHeight
let generalHeight = newHeight + separatorHeight + buttonHeight + abs(bottomSpace)
var frame = self.view.frame
frame.size.height = generalHeight
self.view.frame = frame
}
})
.addDisposableTo(junk)
}
// MARK: - Other methods
fileprivate func setCloseButtonDisabledIfNeeded() {
closeButton?.isEnabled = answerTextView?.isFirstResponder ?? false
}
func rotated() {
answerTextView?.maxLines = appropriateMaxLines
}
}
// MARK: - UITextViewDelegate Protocol Conformance
extension InputViewController: UITextViewDelegate {
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
textView.inputAccessoryView = view
return true
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
textView.inputAccessoryView = nil
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
setCloseButtonDisabledIfNeeded()
}
func textViewDidEndEditing(_ textView: UITextView) {
setCloseButtonDisabledIfNeeded()
}
func textViewDidChange(_ textView: UITextView) {
delegate?.answerTextViewDidChange(textView)
}
}
Im creating chat part of application and i have problem with keyboard animation when user is draging scrollview up and down. I am using keyboardDismissMode = .Interactive and i cant find notification working with it.
This is defaul state. Here i have UIView() user as container for Textview and Button.
and this is my problem when user just slowly scrolling down through the keyboard. I need to move with containerView when keyboard start moving.
I tried UIkeyboardwillChangeFrame but it didnt notificate.
he re sample of my code that i believe is usefull for you.
import UIKit
struct Message {
var reciever: String?
var sender: String?
var text: String?
var time: String?
}
class ChatMessagesVC: UIViewController, UITextViewDelegate,UIScrollViewDelegate {
var chatID: String?
var recieverName: String?
var recieverId: String?
var recieverPhoto: UIImage?
let scrollView: UIScrollView = UIScrollView()
let textView: UITextView = UITextView()
let sendButton: UIButton = UIButton()
var bottomConstraint: NSLayoutConstraint!
var lastMessageFrom: String = ""
var keyboardRect: CGRect!
let SENDER_BACKGROUND_COLOR: UIColor = .whiteColor()
let SENDER_TEXT_COLOR: UIColor = .blackColor()
let SENDER_FONT: UIFont = UIFont(name: "OpenSans", size: 13)!
let RECIEVER_BACKGROUND_COLOR: UIColor = .yellowColor()
let RECIEVER_TEXT_COLOR: UIColor = .blackColor()
let RECIEVER_FONT: UIFont = UIFont(name: "OpenSans", size: 13)!
// place values
var messageX: CGFloat = 75.0
var messageY: CGFloat = 26.0
var imageX: CGFloat = 10
override func viewDidLoad() {
super.viewDidLoad()
if self.recieverName != nil {
self.title = self.recieverName
}
if self.chatID != nil {
DataModel.instance.CHAT.childByAppendingPath(self.chatID).observeEventType(.ChildAdded, withBlock: {snap in
var message = Message(reciever: nil, sender: nil, text: nil, time: snap.key)
if let text = snap.value["text"] as? String {
message.text = text
}
if let sender = snap.value["from"] as? String {
message.sender = sender
}
if let reciever = snap.value["to"] as? String {
message.reciever = reciever
}
if let _ = snap.value["unread"] as? String {
if message.sender != currentUser.id {
DataModel.instance.CHAT.childByAppendingPath(self.chatID).childByAppendingPath(message.time).childByAppendingPath("unread").removeValue()
}
}
self.addMessage(message)
})
}
// notifications about keyboard
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillShow), name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillHide), name:UIKeyboardWillHideNotification, object: nil)
//BG
let backgroundView = UIImageView(frame: self.view.bounds)
let image = UIImage(named: "radarTable")
backgroundView.image = image
self.view.addSubview(backgroundView)
self.textView.delegate = self
self.view.addSubview(self.scrollView)
self.scrollView.backgroundColor = .clearColor()
self.scrollView.delegate = self
self.scrollView.scrollEnabled = true
self.scrollView.keyboardDismissMode = .Interactive
self.textView.font = UIFont(name: "OpenSans", size: 15)
self.textView.textColor = .whiteColor()
self.textView.text = NSLocalizedString("chat.placeholder", comment: "")
self.textView.backgroundColor = .clearColor()
self.textView.returnKeyType = .Send
self.sendButton.setTitleColor(.blackColor(), forState: .Normal)
self.sendButton.setTitle(NSLocalizedString("chat.send", comment: ""), forState: .Normal)
self.sendButton.titleLabel?.font = UIFont(name: "OpenSans", size: 15)
self.sendButton.backgroundColor = .orangeColor()
self.sendButton.addTarget(self, action: #selector(self.sendMessage), forControlEvents: .TouchUpInside)
// divider
let divider = UIView()
divider.backgroundColor = .yellowColor()
self.view.addSubview(divider)
// container
let containerView = UIView()
containerView.addSubview(self.textView)
containerView.addSubview(self.sendButton)
self.view.addSubview(containerView)
// layout
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.textView.translatesAutoresizingMaskIntoConstraints = false
self.sendButton.translatesAutoresizingMaskIntoConstraints = false
divider.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
let binding = ["scroll": self.scrollView, "text": self.textView, "button": self.sendButton, "div":divider, "container": containerView]
// horizontal constraints
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scroll]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[div]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[container]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[text]-5-[button(100)]-10-|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
// vertical constraints
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-50-[scroll]-0-[div(1)]-0-[container(50)]", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[text]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
containerView.addConstraint(NSLayoutConstraint(item: containerView, attribute: .CenterY, relatedBy: .Equal, toItem: self.sendButton, attribute: .CenterY, multiplier: 1, constant: 0))
containerView.addConstraint(NSLayoutConstraint(item: self.sendButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
self.bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1, constant: 0)
self.view.addConstraint(self.bottomConstraint)
}
//MARK: - textview Methods
func textViewDidBeginEditing(textView: UITextView) {
if textView.text == NSLocalizedString("chat.placeholder", comment: "") {
textView.text = nil
}
}
func textViewDidEndEditing(textView: UITextView) {
if textView.text.isEmpty {
textView.text = NSLocalizedString("chat.placeholder", comment: "")
}
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
self.sendMessage()
return false
}
return true
}
//MARK: - scrollview functions
// func scrollViewDidScroll(scrollView: UIScrollView) {
// let location = scrollView.panGestureRecognizer.locationInView(self.view)
// if self.keyboardRect != nil {
// let start = UIScreen.mainScreen().bounds.height - self.keyboardRect.height
// if location.y > start {
// self.bottomConstraint.constant = -self.keyboardRect.height - (start - location.y)
//
// }
// }
// }
//MARK: - keyboard notifications
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.keyboardRect = keyboardSize
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
self.bottomConstraint.constant = -keyboardSize.height
self.view.layoutIfNeeded()
}, completion: nil)
let scroll = CGPointMake(0, self.scrollView.contentSize.height - (self.scrollView.bounds.height))
self.scrollView.setContentOffset(scroll, animated: true)
}
}
func keyboardWillHide(notification: NSNotification) {
self.keyboardRect = nil
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
self.bottomConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: nil)
}
//MARK: - chat functions
func addMessage(message: Message) {
let theWidth = UIScreen.mainScreen().bounds.width
var messagesSpace:CGFloat = 2
if self.lastMessageFrom != message.sender {
//TODO: Dopln cas - neivem ako sa to mas presne spravat a zorbrazovat tak neimplementujem
messagesSpace += 25
}
let messageLbl : UILabel = UILabel()
messageLbl.frame = CGRectMake(0, 0, self.scrollView.frame.size.width - 100, 0)
messageLbl.lineBreakMode = NSLineBreakMode.ByWordWrapping
messageLbl.textAlignment = NSTextAlignment.Left
messageLbl.numberOfLines = 0
messageLbl.text = message.text
messageLbl.sizeToFit()
messageLbl.frame.origin.y = self.messageY + messagesSpace + 5
let frame = UIView()
if message.sender == currentUser.id {
messageLbl.backgroundColor = self.SENDER_BACKGROUND_COLOR
messageLbl.textColor = self.SENDER_TEXT_COLOR
messageLbl.font = self.SENDER_FONT
messageLbl.frame.origin.x = (self.scrollView.frame.size.width - self.messageX) - messageLbl.frame.width
} else {
messageLbl.backgroundColor = self.RECIEVER_BACKGROUND_COLOR
messageLbl.textColor = self.RECIEVER_TEXT_COLOR
messageLbl.font = self.SENDER_FONT
messageLbl.frame.origin.x = self.messageX
}
// if should add photo
if self.lastMessageFrom != message.sender {
let img:UIImageView = UIImageView()
img.frame = CGRectMake(self.imageX, self.messageY + messagesSpace, 50, 50)
self.lastMessageFrom = message.sender!
if message.sender == currentUser.id {
img.frame.origin.x = (self.scrollView.frame.size.width - self.imageX) - img.frame.size.width
img.image = currentUser.photo
} else {
img.image = self.recieverPhoto
}
img.layer.cornerRadius = img.frame.size.width/2
img.clipsToBounds = true
self.scrollView.addSubview(img)
}
let bounds = messageLbl.frame
frame.frame = CGRectMake(bounds.minX - 10, bounds.minY - 5, bounds.width + 14, bounds.height + 10)
frame.backgroundColor = messageLbl.backgroundColor
if message.sender == currentUser.id {
frame.roundCorners([.TopLeft, .BottomRight, .BottomLeft], radius: 10)
} else {
frame.roundCorners([.TopRight, .BottomRight, .BottomLeft], radius: 10)
}
self.scrollView.addSubview(frame)
self.scrollView.addSubview(messageLbl)
self.messageY += frame.frame.size.height + messagesSpace
self.scrollView.contentSize = CGSizeMake(theWidth, self.messageY + messagesSpace)
let bottomOfset:CGPoint = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height)
self.scrollView.setContentOffset(bottomOfset, animated: true)
}
func scrollViewTapped() {
self.textView.resignFirstResponder()
}
func sendButtonPressed() {
textView.resignFirstResponder()
self.sendMessage()
}
func sendMessage() {
if self.textView.text != NSLocalizedString("chat.placeholder", comment: "") && !self.textView.text.isBlank {
let time = NSDate().ToUTCStringWithFormat("yyyy-MM-dd'T'HH:mm:ss")
let result = ["from":currentUser.id, "to": self.recieverId, "text": self.textView.text, "unread": "true"]
DataModel.instance.CHAT.childByAppendingPath(self.chatID).childByAppendingPath(time).setValue(result)
self.textView.text = nil
DataModel.instance.USERS.childByAppendingPath(currentUser.id).childByAppendingPath("chats").childByAppendingPath(self.chatID).setValue(self.recieverId)
DataModel.instance.USERS.childByAppendingPath(self.recieverId).childByAppendingPath("chats").childByAppendingPath(chatID).setValue(currentUser.id)
}
}
If I understand your question correctly, I think what you probably want here is to make your _containerView an inputAccessoryView as described in the Apple docs here:
https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/InputViews/InputViews.html
Once you implement this, you'll get the keyboard behavior you describe because your view will be "attached" to the keyboard.
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 need to scroll data inside UITextfield so that the text of longer width can be seen by scrolling.
Can someone reply me to solve this?
You can use UITextView: Try below one.
UITextView *textView=[[UITextView alloc] initWithFrame:CGRectMake(20, 40, 200, 70)];
textView.font=[UIFont systemFontOfSize:18.0];
textView.userInteractionEnabled=YES;
textView.backgroundColor=[UIColor whiteColor];
textView.scrollEnabled=YES;
textView.delegate = self;
[self.view addSubview:textView];
I add the textField on a UIScrollView, and change the contentSize when the length of the text changed, and scroll the scrollView when the cursor's position changed or selection range changed.
I have made a demo:
private enum TextRangeChangedType: Int {
case leftAndBack = 0
case leftAndForward
case rightAndBack
case rightAndForward
case none
}
public class ScrollableTextField: UIView {
/// Real textFiled.
///
/// You should set delegate, add actions or resign first responder to this view.
private(set) var textField: UITextField?
// MARK: - Private
private let oneCutWidth: CGFloat = UIScreen.main.bounds.width
private let defaultCutTimes: CGFloat = 3
private var scrollView: UIScrollView?
private weak var tapGesture: UITapGestureRecognizer?
private weak var textFiledWidthConstraint: NSLayoutConstraint?
// MARK: - Private funcs
private func configureSubviews() {
//scroll
scrollView = UIScrollView(frame: .zero)
if let scrollView = scrollView {
scrollView.contentSize = CGSize(width: oneCutWidth * defaultCutTimes, height: 0)
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.bounces = false
scrollView.delegate = self
scrollView.backgroundColor = .clear
self.addSubview(scrollView)
// if not using masonry
scrollView.translatesAutoresizingMaskIntoConstraints = false
let scrollLeftConstraint = NSLayoutConstraint(item: scrollView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
let scrollRightConstraint = NSLayoutConstraint(item: scrollView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
let scrollTopConstraint = NSLayoutConstraint(item: scrollView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
let scrollBottomConstraint = NSLayoutConstraint(item: scrollView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
self.addConstraints([scrollLeftConstraint, scrollRightConstraint, scrollTopConstraint, scrollBottomConstraint])
// scrollView.mas_makeConstraints {[weak self] (make) in // if use masonry
// make?.edges.equalTo()(self)
// }
//text field
textField = InnerTextField(frame: .zero)
let oneCut = oneCutWidth
let times = defaultCutTimes
if let textField = textField as? InnerTextField {
textField.addTarget(self, action: #selector(handleTextField(_:)), for: .editingChanged)
let gesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
gesture.delegate = self
textField.addGestureRecognizer(gesture)
tapGesture = gesture
textField.selectedTextRangeChangedBlock = {[weak self] (type, width) in
guard let scrollView = self?.scrollView else {
return
}
let div: CGFloat = 15
if width < scrollView.contentOffset.x + div {
UIView.animate(withDuration: 0.1, animations: {
scrollView.contentOffset.x = max(width - div, 0)
})
} else if width > scrollView.contentOffset.x + scrollView.bounds.width - div {
UIView.animate(withDuration: 0.1, animations: {
scrollView.contentOffset.x = width - scrollView.bounds.width + div
})
}
}
scrollView.addSubview(textField)
// if not using masonry
textField.translatesAutoresizingMaskIntoConstraints = false
let textLeftConstaint = NSLayoutConstraint(item: textField, attribute: .left, relatedBy: .equal, toItem: scrollView, attribute: .left, multiplier: 1, constant: 0)
let textTopConstrait = NSLayoutConstraint(item: textField, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
let textBottomConstrait = NSLayoutConstraint(item: textField, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
let textWidthConstrait = NSLayoutConstraint(item: textField, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: oneCut * times)
scrollView.addConstraint(textLeftConstaint)
self.addConstraints([textTopConstrait, textBottomConstrait])
textField.addConstraint(textWidthConstrait)
textFiledWidthConstraint = textWidthConstrait
// textField.mas_makeConstraints {[weak self] (make) in // if use masonry
// make?.left.equalTo()(scrollView)
// make?.top.and()?.bottom()?.equalTo()(self)
// make?.width.equalTo()(oneCut * times)
// }
}
}
}
// MARK: - Life circle
public override init(frame: CGRect) {
super.init(frame: frame)
configureSubviews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Actions
#objc private func handleTap(_ gesture: UITapGestureRecognizer) {
let point = gesture.location(in: textField)
let cloestPosition = textField?.closestPosition(to: point)
if let cloestPosition = cloestPosition {
textField?.selectedTextRange = textField?.textRange(from: cloestPosition, to: cloestPosition)
}
}
#objc private func handleTextField(_ textField: UITextField) {
guard let scrollView = self.scrollView, let hookedTF = textField as? InnerTextField else {
return
}
guard let width = hookedTF.getWidthFromDocumentBeginingToCursor(), let fullWidth = hookedTF.getWidthFromDocumentBeginingToEnd() else {
return
}
let selfWidth = self.bounds.width
if selfWidth == 0 {
return
}
//check max bounds
if scrollView.contentSize.width - fullWidth < oneCutWidth {
if scrollView.contentSize.width <= fullWidth {
scrollView.contentSize.width = fullWidth + oneCutWidth
} else {
scrollView.contentSize.width += oneCutWidth
}
// if not using masonry
textFiledWidthConstraint?.constant = scrollView.contentSize.width
// textField.mas_updateConstraints { (make) in // if use masonry
// make?.width.equalTo()(scrollView.contentSize.width)
// }
self.layoutIfNeeded()
}
if width >= selfWidth - 3 {
if width - scrollView.contentOffset.x >= 0 && width - scrollView.contentOffset.x < selfWidth {
return
}
let diff = max(width - selfWidth + 3, 0)
scrollView.contentOffset.x = diff
} else {
scrollView.contentOffset.x = 0
}
}
}
// MARK: - Delegate
extension ScrollableTextField: UIScrollViewDelegate, UIGestureRecognizerDelegate {
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
guard let currentTextWidth = (textField as? InnerTextField)?.getWidthFromDocumentBeginingToEnd() else {
return
}
let selfFrame = self.frame
if currentTextWidth < selfFrame.width {
scrollView.contentOffset.x = 0
return
}
let maxOffsetX = currentTextWidth - selfFrame.width + 6
if scrollView.contentOffset.x > maxOffsetX {
scrollView.contentOffset.x = maxOffsetX
}
}
public override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer == self.tapGesture {
if self.textField?.isFirstResponder ?? false {
return true
} else {
return false
}
}
return super.gestureRecognizerShouldBegin(gestureRecognizer)
}
}
// MARK: - Private class
private class InnerTextField: UITextField {
// MARK: - Public
func getWidthFromDocumentBeginingToCursor() -> CGFloat? {
guard let selectedRange = self.selectedTextRange else {
return nil
}
let width = getWidthFromDocumentBegining(to: selectedRange.start)
return width
}
func getWidthFromDocumentBeginingToEnd() -> CGFloat? {
guard let str = self.text else {
return nil
}
let width = getWidthFrom(string: str)
return width
}
// MARK: - Private
private func changeType(oldRange: UITextRange, newRange: UITextRange) -> TextRangeChangedType {
let oldStart = self.offset(from: beginningOfDocument, to: oldRange.start)
let oldEnd = self.offset(from: beginningOfDocument, to: oldRange.end)
let newStart = self.offset(from: beginningOfDocument, to: newRange.start)
let newEnd = self.offset(from: beginningOfDocument, to: newRange.end)
if (oldStart == newStart) && (oldEnd != newEnd) {
if (newEnd > oldEnd) {
return .rightAndForward
} else if (newEnd < oldEnd) {
return .rightAndBack
}
return .none
}
if (oldStart != newStart) && (oldEnd == newEnd) {
if (newStart < oldStart) {
return .leftAndBack
} else if (newStart > oldStart) {
return .leftAndForward
}
return .none
}
if (oldStart == oldEnd) && (newStart == newEnd) {
if newStart > oldStart {
return .rightAndForward
} else if newStart < oldStart {
return .leftAndBack
}
}
return .none
}
private func getWidthFrom(string text: String) -> CGFloat {
let label = UILabel(frame: .zero)
label.text = text
var defaultFont = UIFont.systemFont(ofSize: 15)
if let font = self.font {
defaultFont = font
}
label.font = defaultFont
label.sizeToFit()
let width = label.bounds.size.width
return width
}
private func getWidthFromDocumentBegining(to position: UITextPosition) -> CGFloat? {
if let textStr = self.text {
let curText = textStr as NSString
let offset = self.offset(from: beginningOfDocument, to: position)
guard offset <= curText.length && offset >= 0 else {
return nil
}
let subStr = curText.substring(to: offset)
let width = getWidthFrom(string: subStr)
return width
}
return nil
}
override var text: String? {
didSet {
self.sendActions(for: .editingChanged)
}
}
override var selectedTextRange: UITextRange? {
willSet {
if let old = selectedTextRange, let `new` = newValue {
let willChangeType = changeType(oldRange: old, newRange: new)
if willChangeType == .leftAndBack || willChangeType == .leftAndForward {
if let width = getWidthFromDocumentBegining(to: new.start) {
selectedTextRangeChangedBlock?(willChangeType, width)
}
} else if willChangeType == .rightAndForward || willChangeType == .rightAndBack {
if let width = getWidthFromDocumentBegining(to: new.end) {
selectedTextRangeChangedBlock?(willChangeType, width)
}
}
}
}
}
var selectedTextRangeChangedBlock: ((_ changType: TextRangeChangedType, _ beforeTextWidth: CGFloat) -> ())?
}
or you can have a preview on my github: https://github.com/sunshuyao/ScrollableTextField
You can do it by this way.
UITextView *textField = [[UITextView alloc]initWithFrame:CGRectMake(100, 100, 60, 50)];
textField.text = #"I need to scroll data inside UITextfield so that the text of longer width can be seen by scrolling.Can someone reply me to solve this.";
textField.delegate = self;
[self.view addSubview:textField];