UISegmentedControl with views animation code review please - ios

I am just trying things out in Swift, and just made my first animation using UISegmentedControl, that switch between view, can you help me with a little code review to know that I am on the right track?
import UIKit;
class ViewLikes:UIViewController {
var innerViews:[UIViewController] = [UIViewController]()
var currentIndex:Int = 0;
var centerView:UIViewController = UIViewController()
override func viewDidLoad() {
super.viewDidLoad()
title = "some title";
var view1:UIViewController = UIViewController()
var lab1:UILabel = UILabel(frame: CGRectMake(0, 10, 200, 21))
lab1.center = CGPointMake(160, 284)
lab1.textAlignment = NSTextAlignment.Center
lab1.text = "I'am a label1"
view1.view.addSubview(lab1)
var view2:UIViewController = UIViewController()
var lab2:UILabel = UILabel(frame: CGRectMake(0, 10, 200, 21))
lab2.center = CGPointMake(160, 284)
lab2.textAlignment = NSTextAlignment.Center
lab2.text = "I'am a label2"
view2.view.addSubview(lab2)
var view3:UIViewController = UIViewController()
var lab3:UILabel = UILabel(frame: CGRectMake(0, 10, 200, 21))
lab3.center = CGPointMake(160, 284)
lab3.textAlignment = NSTextAlignment.Center
lab3.text = "I'am a label3"
view3.view.addSubview(lab3)
innerViews.append(view1)
innerViews.append(view2)
innerViews.append(view3)
var segmentControl:UISegmentedControl = UISegmentedControl(items:["blah", "blah1", "blah2"]);
segmentControl.selectedSegmentIndex = currentIndex;
segmentControl.addTarget(self, action:"segmentSwitch:" , forControlEvents:UIControlEvents.ValueChanged )
segmentControl.setTranslatesAutoresizingMaskIntoConstraints(false)
self.centerView.view.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(segmentControl)
self.view.addSubview(self.centerView.view)
var currentView = self.innerViews[0]
currentView.viewWillAppear(false)
self.centerView.view.addSubview(currentView.view)
currentView.viewDidAppear(false)
//Set layout
var viewsDict = Dictionary <String, UIView>()
viewsDict["segment"] = segmentControl;
viewsDict["center"] = self.centerView.view;
self.view.addConstraint(NSLayoutConstraint(item: segmentControl,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: self.topLayoutGuide,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1, constant: 10))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-[segment]-|",
options: NSLayoutFormatOptions(0),
metrics: nil,
views: viewsDict))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-0-[center]-0-|",
options: NSLayoutFormatOptions(0),
metrics: nil,
views: viewsDict))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[segment]-[center]-0-|",
options: NSLayoutFormatOptions(0),
metrics: nil,
views: viewsDict))
}
func segmentSwitch(control:UISegmentedControl) {
selectView(control.selectedSegmentIndex)
}
func selectView(index:Int) {
var currentView = self.innerViews[self.currentIndex]
var nextView = self.innerViews[index]
nextView.viewWillAppear(false)
self.centerView.view.addSubview(nextView.view)
nextView.viewDidAppear(false)
if(index > currentIndex) {
nextView.view.frame.origin.x = nextView.view.frame.width;
} else {
nextView.view.frame.origin.x -= nextView.view.frame.width;
}
UIView.animateWithDuration(0.8,
animations: {
var currentFrame = currentView.view.frame;
var nextFrame = nextView.view.frame;
if(index > self.currentIndex) {
currentFrame.origin.x -= currentFrame.size.width;
} else {
currentFrame.origin.x = currentFrame.size.width;
}
nextFrame.origin.x = 0.0;
currentView.view.frame = currentFrame
nextView.view.frame = nextFrame
}, completion: {finished in
currentView.viewWillDisappear(false)
currentView.view.removeFromSuperview()
currentView.viewDidDisappear(false)
}
);
self.currentIndex = index
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
for innerView in self.innerViews {
innerView.didReceiveMemoryWarning();
}
}
}

Related

Cannot add NSLayoutContraints for a custom UIButton

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
}()

How to animate keyboard scrolling with keyboardDismissMode = .interactive

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.

subclass of a generic UIViewController subclass isn't available in InterfaceBuilder [duplicate]

This question already has answers here:
Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController
(2 answers)
Closed 6 years ago.
I have a generic UIViewController subclass that expects a type that is a subclass of UIView and also implements a specific protocol. I have a subclass of that generic UIViewController subclass. I have a view controller in a story board with it's type set to MyKeyboardAwareScrollViewController (the subclass of the generic UIViewController subclass). When I run my program I get a message:
Unknown class _TtC19ExploreCodeCoverage35MyKeyboardAwareScrollViewController in Interface Builder file.
Any ideas why/how to fix?
A protocol:
protocol MyProtocol{
func widgets() -> Void
func activeView() -> UIView?
}
A Generic View Controller Subclass (that requires a type that is a UIView implementing the protocol MyProtocol):
class KeyboardAwareScrollViewController<T:UIView where T:MyProtocol>: UIViewController {
private let titleView: UIView
private let contentView: T
private weak var scrollView: UIScrollView?
private weak var titleViewContainer: UIView?
init(titleView:UIView, contentView: T){
self.titleView = titleView
self.contentView = contentView
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
self.titleView = aDecoder.decodeObjectOfClass(UIView.self, forKey: "titleView")!
self.contentView = aDecoder.decodeObjectOfClass(T.self, forKey: "contentView")!
super.init(coder: aDecoder)
}
override func viewWillAppear(animated: Bool) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardShown:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardShown(notification: NSNotification){
let kbSize = (notification.userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
if let scrollView = scrollView{
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: kbSize.height, right: 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
if let activeField = self.contentView.activeView(){
let convertedFrame = scrollView.convertRect(activeField.superview!.bounds, fromView: activeField.superview!)
let offsetFrame = CGRectOffset(convertedFrame, 0.0, 19.0)
let frame = CGRectInset(offsetFrame, 0.0, 17.0)
scrollView.scrollRectToVisible(frame, animated: false)
}
}
}
func keyboardHidden(notification: NSNotification){
if let scrollView = scrollView{
let insets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0)
scrollView.contentInset = insets
scrollView.scrollIndicatorInsets = insets
}
}
override func loadView() {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = UIColor.brownColor()
let sv = UIScrollView()
sv.backgroundColor = UIColor.clearColor()
sv.opaque = false
sv.translatesAutoresizingMaskIntoConstraints = false
sv.alwaysBounceVertical = true
self.scrollView = sv
let titleContainer = buildTitleContainer(self.titleView)
view.addSubview(titleContainer)
view.addSubview(sv)
//peg scroll view to container.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: ["scrollView":sv]))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[scrollView]|", options:NSLayoutFormatOptions(rawValue: 0), metrics:nil, views: ["scrollView":sv]))
let contentViewWrapper = UIView()
contentViewWrapper.translatesAutoresizingMaskIntoConstraints = false
contentViewWrapper.addSubview(self.contentView)
contentViewWrapper.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|->=0-[contentView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["contentView": contentView]))
contentViewWrapper.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[contentView]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["contentView": contentView]))
sv.addSubview(contentViewWrapper)
let viewsDictionary = [
"scrollView": sv,
"view": view,
"contentViewWrapper": contentViewWrapper
]
//peg contentView to be at least as tall as view.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[contentViewWrapper(>=view)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary))
//peg contentView to be equal to host view width
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[contentViewWrapper(==view)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: viewsDictionary))
//peg title container to width of container.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[titleContainer]|",
options:NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: ["titleContainer":titleContainer]))
//title view height.
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[titleContainer][contentView]",
options:NSLayoutFormatOptions(rawValue: 0),
metrics: nil,
views: ["titleContainer":titleContainer, "contentView":self.contentView]))
self.view = view
self.titleViewContainer = titleContainer
}
func buildTitleContainer(titleView:UIView) -> UIView{
let titleContainer = UIView()
titleContainer.translatesAutoresizingMaskIntoConstraints = false
titleContainer.backgroundColor = UIColor.orangeColor()
titleContainer.addSubview(titleView)
titleContainer.setContentCompressionResistancePriority(1000.0, forAxis: .Vertical)
titleContainer.addConstraint(NSLayoutConstraint(item: titleView, attribute: .CenterY, relatedBy: .Equal, toItem: titleContainer, attribute: .CenterY, multiplier: 1.0, constant: 0.0))
titleContainer.addConstraint(NSLayoutConstraint(item: titleView, attribute: .CenterX, relatedBy: .Equal, toItem: titleContainer, attribute: .CenterX, multiplier: 1.0, constant: 0.0))
return titleContainer;
}
}
A UIView subclass that implements the protocol:
class MyContentView:UIView, MyProtocol, UITextFieldDelegate{
weak var myActiveView:UIView?
init(){
super.init(frame: CGRectZero)
self.backgroundColor = UIColor.clearColor()
self.opaque = false
self.translatesAutoresizingMaskIntoConstraints = false
let cells = Array(1...12).map{_ in cellView() }
var cellsDictionary: Dictionary<String, UIView> = [:], cellsVerticalFormat = "V:|-"
for (idx, cell) in cells.enumerate(){
self.addSubview(cell)
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[cell]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["cell": cell]))
let cellKey = "cell\(idx)"
cellsDictionary[cellKey] = cell
cellsVerticalFormat = cellsVerticalFormat + "[\(cellKey)]-"
}
cellsVerticalFormat = cellsVerticalFormat + "|"
self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(cellsVerticalFormat, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: cellsDictionary))
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func cellView() -> UIView{
let result = UIView()
result.backgroundColor = UIColor.magentaColor()
result.translatesAutoresizingMaskIntoConstraints = false
let text = UITextField()
text.translatesAutoresizingMaskIntoConstraints = false
result.addSubview(text)
text.delegate = self
result.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-[text]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["text": text]))
result.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-[text]-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: ["text": text]))
return result
}
func widgets() {
print("\(MyContentView.self) widgets")
}
func activeView() -> UIView? {
return myActiveView
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool{
myActiveView = textField.superview!
return true
}
func textFieldDidEndEditing(textField: UITextField) {
if myActiveView === textField.superview! {
myActiveView = nil
}
}
func textFieldShouldEndEditing(textField: UITextField) -> Bool {
return true
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return false
}
}
A KeyboardAwareViewController subclass:
class MyKeyboardAwareScrollViewController : KeyboardAwareScrollViewController<MyContentView>{
let myContentView: MyContentView = MyContentView()
let myTitleView: UILabel = {
let result = UILabel()
result.translatesAutoresizingMaskIntoConstraints = false
result.text = "Pretend This Says\nCohn-Reznick"
result.numberOfLines = 0
result.textAlignment = .Center
return result
}()
required init?(coder aDecoder: NSCoder) {
super.init(titleView: myTitleView, contentView:myContentView)
}
}
Answer is here: Xcode 7 Swift 2 impossible to instantiate UIViewController subclass of generic UITableViewController
TLDR; Objective-c and InterfaceBuilder can't "see" generic swift classes.

How to scroll text inside uitextfield iOS

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];

How to display activity indicator in center of UIAlertController?

I currently have a UIAlertController being displayed on the screen. The view of the alert should only display 2 elements, a title and a UIActivityIndicatorView in the center of the alert. Below is the function that displays the alert and its elements.
func displaySignUpPendingAlert() -> UIAlertController {
//Create the UIAlertController
let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
//Create the activity indicator to display in it.
let indicator = UIActivityIndicatorView(frame: CGRectMake(pending.view.frame.width / 2.0, pending.view.frame.height / 2.0, 20.0, 20.0))
indicator.center = CGPointMake(pending.view.frame.width / 2.0, pending.view.frame.height / 2.0)
//Add the activity indicator to the alert's view
pending.view.addSubview(indicator)
//Start animating
indicator.startAnimating()
self.presentViewController(pending, animated: true, completion: nil)
return pending
}
However, the activity indicator doesn't display in the center of the view, in fact it displays in the bottom right of the screen, far off of the view. What is the reason for this?
EDIT: I understand that I can hardcode numbers for the indicator's position, but I want the alert to work on multiple devices with multiple screen sizes and orientations.
Be sure to set the frame property when you're creating a view.
func displaySignUpPendingAlert() -> UIAlertController {
//create an alert controller
let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]
//add the activity indicator as a subview of the alert controller's view
pending.view.addSubview(indicator)
indicator.isUserInteractionEnabled = false // required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
self.presentViewController(pending, animated: true, completion: nil)
return pending
}
To #62Shark:
let pending = UIAlertController(title: "Creating New User", message: nil, preferredStyle: .Alert)
let indicator = UIActivityIndicatorView()
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
pending.view.addSubview(indicator)
let views = ["pending" : pending.view, "indicator" : indicator]
var constraints = NSLayoutConstraint.constraintsWithVisualFormat("V:[indicator]-(-50)-|", options: nil, metrics: nil, views: views)
constraints += NSLayoutConstraint.constraintsWithVisualFormat("H:|[indicator]|", options: nil, metrics: nil, views: views)
pending.view.addConstraints(constraints)
indicator.userInteractionEnabled = false
indicator.startAnimating()
self.presentViewController(pending, animated: true, completion: nil)
I converted the answer to Objective C, if anyone is interested:
UIAlertController *pending = [UIAlertController alertControllerWithTitle:nil
message:#"Please wait...\n\n"
preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView* indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
indicator.color = [UIColor blackColor];
indicator.translatesAutoresizingMaskIntoConstraints=NO;
[pending.view addSubview:indicator];
NSDictionary * views = #{#"pending" : pending.view, #"indicator" : indicator};
NSArray * constraintsVertical = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[indicator]-(20)-|" options:0 metrics:nil views:views];
NSArray * constraintsHorizontal = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[indicator]|" options:0 metrics:nil views:views];
NSArray * constraints = [constraintsVertical arrayByAddingObjectsFromArray:constraintsHorizontal];
[pending.view addConstraints:constraints];
[indicator setUserInteractionEnabled:NO];
[indicator startAnimating];
[self presentViewController:pending animated:YES completion:nil];
Cheers
tl;dr
All the other answers are off :) See documentation:
Important
The UIAlertController class is intended to be used as-is and does not
support subclassing. The view hierarchy for this class is private and
must not be modified.
Problem
The problem is not the UIAlertController. This is a very simple UI, a stackview or two depending if you want the UIActivityIndicatorView left to the title label or under the title. The presentation animation is what we want.
The code below is based on the WWDC session A Look Inside Presentation Controllers.
Swift
Recreate Presentation Controller:
class LOActivityAlertControllerPresentationController: UIPresentationController {
var dimmerView: UIView!
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
self.dimmerView = UIView()
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
dimmerView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
dimmerView.backgroundColor = UIColor.init(white: 0, alpha: 0.4)
guard let presentedView = self.presentedView else { return }
presentedView.layer.cornerRadius = 8.0
let centerXMotionEffect: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
centerXMotionEffect.minimumRelativeValue = -10.0
centerXMotionEffect.maximumRelativeValue = 10.0
let centerYMotionEffect: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
centerYMotionEffect.minimumRelativeValue = -10.0
centerYMotionEffect.maximumRelativeValue = 10.0
let group: UIMotionEffectGroup = UIMotionEffectGroup()
group.motionEffects = [centerXMotionEffect, centerYMotionEffect]
presentedView.addMotionEffect(group)
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let containerView = self.containerView, let presentedView = self.presentedView else { return .zero }
let size = presentedView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
var frame = CGRect.zero
frame.origin = CGPoint(x: containerView.frame.midX - (size.width / 2.0), y: containerView.frame.midY - (size.height / 2.0))
frame.size = size
return frame
}
override func presentationTransitionWillBegin() {
guard let containerView: UIView = self.containerView, let presentedView: UIView = self.presentedView, let dimmerView = self.dimmerView else { return }
let presentingViewController: UIViewController = self.presentingViewController
dimmerView.alpha = 0.0
dimmerView.frame = containerView.bounds
containerView.insertSubview(dimmerView, at: 0)
presentedView.center = containerView.center
guard let transitionCoordinator = presentingViewController.transitionCoordinator else { return }
transitionCoordinator.animate(
alongsideTransition: { _ in
dimmerView.alpha = 1.0
},
completion: nil
)
}
override func containerViewWillLayoutSubviews() {
super.containerViewWillLayoutSubviews()
guard let containerView: UIView = self.containerView, let presentedView: UIView = self.presentedView, let dimmerView = self.dimmerView else { return }
dimmerView.frame = containerView.bounds
presentedView.frame = self.frameOfPresentedViewInContainerView
}
override func dismissalTransitionWillBegin() {
guard let dimmerView = self.dimmerView, let transitionCoordinator = self.presentingViewController.transitionCoordinator else { return }
transitionCoordinator.animate(
alongsideTransition: { _ in
dimmerView.alpha = 0.0
},
completion: nil
)
}
}
Animated Transitioning:
class LOActivityAlertControllerAnimatedTransitioning: NSObject, UIViewControllerAnimatedTransitioning {
var presentation: Bool
init(presentation: Bool) {
self.presentation = presentation
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
let containerView = transitionContext.containerView
guard let fromView = transitionContext.view(forKey: .from), let toView = transitionContext.view(forKey: .to) else { return }
if self.presentation {
containerView.addSubview(toView)
toView.transform = CGAffineTransform(scaleX: 1.6, y: 1.6)
toView.alpha = 0.0
UIView.animate(
withDuration: 0.2,
animations: {
toView.alpha = 1.0
toView.transform = .identity
},
completion: { finished in
transitionContext.completeTransition(true)
}
)
} else {
UIView.animate(
withDuration: 0.2,
animations: {
fromView.alpha = 0.0
},
completion: { finished in
fromView.removeFromSuperview()
transitionContext.completeTransition(true)
}
)
}
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.2
}
}
Sample UIViewController subclass, season to taste with XIB:
class LOActivityAlertController: UIViewController, UIViewControllerTransitioningDelegate {
var activityIndicatorView: UIActivityIndicatorView!
var titleLabel: UILabel!
var messageLabel: UILabel!
var alertTitle: String
var alertMessage: String
init(title: String, message: String) {
self.alertTitle = title
self.alertMessage = message
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("Not implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.transitioningDelegate = self
self.modalPresentationStyle = .custom
self.titleLabel = UILabel()
self.messageLabel = UILabel()
self.titleLabel.text = self.alertTitle
self.messageLabel.text = self.alertMessage
self.activityIndicatorView = UIActivityIndicatorView(style: .medium)
let currentFrame = self.view.frame
let alertFrame = CGRect(x: 0, y: 0, width: currentFrame.width / 2.0, height: currentFrame.height / 2.0)
let stackView = UIStackView(frame: alertFrame)
stackView.backgroundColor = .gray
stackView.axis = .vertical
stackView.alignment = .center
stackView.distribution = .fillProportionally
stackView.addArrangedSubview(self.titleLabel)
stackView.addArrangedSubview(self.messageLabel)
stackView.addArrangedSubview(self.activityIndicatorView)
self.activityIndicatorView.startAnimating()
self.view.addSubview(stackView)
}
override func viewDidAppear(_ animated: Bool) {
}
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
let presentationController = LOActivityAlertControllerPresentationController(presentedViewController: presented, presenting: presenting)
return presentationController
}
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitioning = LOActivityAlertControllerAnimatedTransitioning(presentation: true)
return transitioning
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
let transitioning = LOActivityAlertControllerAnimatedTransitioning(presentation: false)
return transitioning
}
}
Credits for swift version: #riciloma
Objective-C
Recreate Presentation Controller:
#interface LOActivityAlertControllerPresentationController : UIPresentationController
#end
#interface LOActivityAlertControllerPresentationController ()
#property (nonatomic) UIView *dimmerView;
#end
#implementation LOActivityAlertControllerPresentationController
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController
{
self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
if (self)
{
_dimmerView = [[UIView alloc] init];
_dimmerView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
_dimmerView.backgroundColor = [UIColor colorWithWhite:0 alpha:0.4];
UIView *presentedView = [self presentedView];
presentedView.layer.cornerRadius = 8.0;
UIInterpolatingMotionEffect *centerXMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.x" type:UIInterpolatingMotionEffectTypeTiltAlongHorizontalAxis];
centerXMotionEffect.minimumRelativeValue = #(-10.0);
centerXMotionEffect.maximumRelativeValue = #(10.0);
UIInterpolatingMotionEffect *centerYMotionEffect = [[UIInterpolatingMotionEffect alloc] initWithKeyPath:#"center.y" type:UIInterpolatingMotionEffectTypeTiltAlongVerticalAxis];
centerYMotionEffect.minimumRelativeValue = #(-10.0);
centerYMotionEffect.maximumRelativeValue = #(10.0);
UIMotionEffectGroup *group = [[UIMotionEffectGroup alloc] init];
group.motionEffects = [NSArray arrayWithObjects:centerXMotionEffect, centerYMotionEffect, nil];
[presentedView addMotionEffect:group];
}
return self;
}
- (CGRect)frameOfPresentedViewInContainerView
{
UIView *containerView = [self containerView];
UIView *presentedView = [self presentedView];
CGSize size = [presentedView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
CGRect frame = CGRectZero;
frame.origin = CGPointMake(CGRectGetMidX([containerView frame]) - (size.width / 2.0),
CGRectGetMidY([containerView frame]) - (size.height / 2.0));
frame.size = size;
return frame;
}
- (void)presentationTransitionWillBegin
{
UIViewController *presentingViewController = [self presentingViewController];
UIView *containerView = [self containerView];
UIView *presentedView = [self presentedView];
UIView *dimmerView = [self dimmerView];
dimmerView.alpha = 0.0;
dimmerView.frame = [containerView bounds];
[containerView insertSubview:dimmerView atIndex:0];
presentedView.center = [containerView center];
[[presentingViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
dimmerView.alpha = 1.0;
} completion:NULL];
}
- (void)containerViewWillLayoutSubviews
{
[super containerViewWillLayoutSubviews];
UIView *containerView = [self containerView];
UIView *presentedView = [self presentedView];
UIView *dimmerView = [self dimmerView];
dimmerView.frame = [containerView bounds];
presentedView.frame = [self frameOfPresentedViewInContainerView];
}
- (void)dismissalTransitionWillBegin
{
UIViewController *presentingViewController = [self presentingViewController];
UIView *dimmerView = [self dimmerView];
[[presentingViewController transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
dimmerView.alpha = 0.0;
} completion:NULL];
}
#end
Animated Transitioning:
#interface LOActivityAlertControllerAnimatedTransitioning : NSObject <UIViewControllerAnimatedTransitioning>
#property (getter=isPresentation) BOOL presentation;
#end
#implementation LOActivityAlertControllerAnimatedTransitioning
- (void)animateTransition:(nonnull id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *containerView = [transitionContext containerView];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
if (_presentation)
{
[containerView addSubview:toView];
toView.transform = CGAffineTransformMakeScale(1.6, 1.6);
toView.alpha = 0.0;
[UIView animateWithDuration:0.2 animations:^{
toView.alpha = 1.0;
toView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
else
{
[UIView animateWithDuration:0.2 animations:^{
fromView.alpha = 0.0;
} completion:^(BOOL finished) {
[fromView removeFromSuperview];
[transitionContext completeTransition:YES];
}];
}
}
- (NSTimeInterval)transitionDuration:(nullable id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.2;
}
#end
Sample UIViewController subclass, season to taste with XIB:
#interface LOActivityAlertController : UIViewController <UIViewControllerTransitioningDelegate>
#property (nonatomic, strong) IBOutlet UIActivityIndicatorView *activityIndicatorView;
#property (nonatomic, strong) IBOutlet UILabel *titleLabel;
#end
#implementation LOActivityAlertController
#dynamic title;
+ (instancetype)alertControllerWithTitle:(NSString *)title
{
LOActivityAlertController *alert = [LOActivityAlertController new];
alert.title = title;
return alert;
}
- (instancetype)init
{
self = [super init];
if (self)
{
self.transitioningDelegate = self;
self.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.titleLabel.text = self.title;
}
#pragma mark Properties
- (void)setTitle:(NSString *)title
{
[super setTitle:title];
self.titleLabel.text = title;
}
#pragma mark UIViewControllerTransitioningDelegate
- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented
presentingViewController:(UIViewController *)presenting
sourceViewController:(UIViewController *)source
{
LOActivityAlertControllerPresentationController *myPresentation = nil;
myPresentation = [[LOActivityAlertControllerPresentationController alloc]
initWithPresentedViewController:presented presentingViewController:presenting];
return myPresentation;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
{
LOActivityAlertControllerAnimatedTransitioning *transitioning = [LOActivityAlertControllerAnimatedTransitioning new];
transitioning.presentation = YES;
return transitioning;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
LOActivityAlertControllerAnimatedTransitioning *transitioning = [LOActivityAlertControllerAnimatedTransitioning new];
return transitioning;
}
#end
Screen Recording
Bug Reporter
rdar://37433306: Make UIAlertController presentation controller and transitioning delegate public API to enable reuse.
Swift 5.0 solution
let alert = UIAlertController(title: "Sender ...", message: nil, preferredStyle: .alert)
let activityIndicator = UIActivityIndicatorView(style: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false
activityIndicator.startAnimating()
alert.view.addSubview(activityIndicator)
alert.view.heightAnchor.constraint(equalToConstant: 95).isActive = true
activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor, constant: 0).isActive = true
activityIndicator.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -20).isActive = true
present(alert, animated: true)
I have to implement NSLayoutConstraints to put the UIActivityIndicatorView on the center of the UIAlertController
For Swift:
let loadingAlertController: UIAlertController = UIAlertController(title: "Loading", message: nil, preferredStyle: .alert)
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView(style: .gray)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
loadingAlertController.view.addSubview(activityIndicator)
let xConstraint: NSLayoutConstraint = NSLayoutConstraint(item: activityIndicator, attribute: .centerX, relatedBy: .equal, toItem: loadingAlertController.view, attribute: .centerX, multiplier: 1, constant: 0)
let yConstraint: NSLayoutConstraint = NSLayoutConstraint(item: activityIndicator, attribute: .centerY, relatedBy: .equal, toItem: loadingAlertController.view, attribute: .centerY, multiplier: 1.4, constant: 0)
NSLayoutConstraint.activate([ xConstraint, yConstraint])
activityIndicator.isUserInteractionEnabled = false
activityIndicator.startAnimating()
let height: NSLayoutConstraint = NSLayoutConstraint(item: loadingAlertController.view, attribute: NSLayoutConstraint.Attribute.height, relatedBy: NSLayoutConstraint.Relation.equal, toItem: nil, attribute: NSLayoutConstraint.Attribute.notAnAttribute, multiplier: 1, constant: 80)
loadingAlertController.view.addConstraint(height)
self.present(loadingAlertController, animated: true, completion: nil)
Result:
For those like me who prefer UIActivityIndicatorView aligned at the left of the UIAlertController.title, this is my solution in Swift working for all devices:
let alert = UIAlertController(title: NSLocalizedString("Authenticating...", comment: "Authenticating"), message: nil, preferredStyle: .Alert);
let activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
activityIndicator.frame = activityIndicator.frame.rectByOffsetting(dx: 8, dy: (alert.view.bounds.height - activityIndicator.frame.height)/2);
activityIndicator.autoresizingMask = .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin
activityIndicator.color = themeManager().currentTheme.navigationBarTintColor;
activityIndicator.startAnimating();
alert.view.addSubview(activityIndicator);
self.presentViewController(progressAlert, animated: true, completion: nil);
However, to align the UIActivityIndicatorView in the view center you can change as follows:
activityIndicator.center = CGPoint(x: (alert.view.bounds.width)/2, y: (alert.view.bounds.height)/2)
activityIndicator.autoresizingMask = .FlexibleLeftMargin | .FlexibleRightMargin | .FlexibleTopMargin | .FlexibleBottomMargin
Apple does not encourage directly subclassing UIAlertController so I made a class that displays UIAlertController with centered UIActivityIndicator and handles the cancel condition with a class protocol.
import Foundation
import UIKit
protocol BusyAlertDelegate {
func didCancelBusyAlert()
}
class BusyAlert {
var busyAlertController: UIAlertController?
var presentingViewController: UIViewController?
var activityIndicator: UIActivityIndicatorView?
var delegate:BusyAlertDelegate?
init (title:String, message:String, presentingViewController: UIViewController) {
busyAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
busyAlertController!.addAction(UIAlertAction(title: NSLocalizedString("Cancel", comment: "Cancel Button"), style: UIAlertActionStyle.Cancel, handler:{(alert: UIAlertAction!) in
delegate?.didCancelBusyAlert()
}))
self.presentingViewController = presentingViewController
activityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.Gray)
busyAlertController!.view.addSubview(activityIndicator!)
}
func display() {
dispatch_async(dispatch_get_main_queue(), {
self.presentingViewController!.presentViewController(self.busyAlertController!, animated: true, completion: {
self.activityIndicator!.translatesAutoresizingMaskIntoConstraints = false
self.busyAlertController!.view.addConstraint(NSLayoutConstraint(item: self.activityIndicator!, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: self.busyAlertController!.view, attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
self.busyAlertController!.view.addConstraint(NSLayoutConstraint(item: self.activityIndicator!, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: self.busyAlertController!.view, attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
self.activityIndicator!.startAnimating()
})
})
}
func dismiss() {
dispatch_async(dispatch_get_main_queue(), {
self.busyAlertController?.dismissViewControllerAnimated(true, completion: nil)
})
}
}
I recommend using lazy var to initialize the class.
lazy var busyAlertController: BusyAlert = {
let busyAlert = BusyAlert(title: "Lengthy Task", message: "Please wait...", presentingViewController: self)
busyAlert.delegate = self
return busyAlert
}()
Here is a link to sample code: https://github.com/cgilleeny/BusyAlertExample.git
It's this simple.
fully tested ...
extension UIViewController {
func verySimpleSpinner() -> UIAlertController {
let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
let spinner = UIActivityIndicatorView(style: .medium)
spinner.startAnimating()
alert.view.addSubview(spinner)
spinner.bindEdgesToSuperview()
present(alert, animated: true, completion: nil)
return alert
}
}
It's impossible to write iOS apps unless you have a simple .bindEdgesToSuperview() call -
extension UIView {
func bindEdgesToSuperview() {
guard let s = superview else { preconditionFailure("flop") }
translatesAutoresizingMaskIntoConstraints = false
leadingAnchor.constraint(equalTo: s.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: s.trailingAnchor).isActive = true
topAnchor.constraint(equalTo: s.topAnchor).isActive = true
bottomAnchor.constraint(equalTo: s.bottomAnchor).isActive = true
}
}
If you want text
If you do also want text, use the excellent code from #magnuskahr. Modernized:
fully tested ...
extension UIView {
func verySimpleSpinner() -> UIAlertController {
let alert = UIAlertController(title: "", message: "Connecting...", preferredStyle: .alert)
let spinner = UIActivityIndicatorView(style: .medium)
alert.view.addSubview(spinner)
alert.view.heightAnchor.constraint(equalToConstant: 95).isActive = true
spinner.translatesAutoresizingMaskIntoConstraints = false
spinner.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor, constant: 0).isActive = true
spinner.bottomAnchor.constraint(equalTo: alert.view.bottomAnchor, constant: -20).isActive = true
spinner.startAnimating()
present(alert, animated: true, completion: nil)
return alert
}
}
How to use
In any view controller:
let spinny = verySimpleSpinner()
when the connection/etc has finished:
spinny.dismiss(animated: true)
In swift:
activityIndicator.center = self.view.center
If you have a tool bar or a navController you might want to shift the point but otherwise, center is center...
If you still have issues, perhaps this tutorial would help. If you are trying to center it in a table view controller, this answer might help.
If you want a ActivityIndicatorView only alert then try this.
func presentLoader() {
let alert = UIAlertController(title: nil, message: "", preferredStyle: .alert)
let activityIndicator = UIActivityIndicatorView(style: .large)
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
activityIndicator.isUserInteractionEnabled = false
activityIndicator.color = .blue
activityIndicator.startAnimating()
alert.view.addSubview(activityIndicator)
NSLayoutConstraint.activate([
alert.view.heightAnchor.constraint(equalToConstant: 95),
alert.view.widthAnchor.constraint(equalToConstant: 95),
activityIndicator.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor),
activityIndicator.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor)
])
present(alert, animated: true)
}
Result:
Converted #petesalt's answer to Swift 3:
let pending = UIAlertController(title: "Saving, please wait...", message: nil, preferredStyle: .alert)
let indicator = UIActivityIndicatorView()
indicator.translatesAutoresizingMaskIntoConstraints = false
pending.view.addSubview(indicator)
let views = ["pending" : pending.view, "indicator" : indicator]
var constraints = NSLayoutConstraint.constraints(withVisualFormat: "V:[indicator]-(-50)-|", options: NSLayoutFormatOptions.alignAllCenterY, metrics: nil, views: views)
constraints += NSLayoutConstraint.constraints(withVisualFormat: "H:|[indicator]|", options: NSLayoutFormatOptions.alignAllCenterX, metrics: nil, views: views)
pending.view.addConstraints(constraints)
indicator.isUserInteractionEnabled = false
indicator.startAnimating()
self.present(pending, animated: true, completion: nil)
How about this way for Swift 3 and higher:
func showActivityIndiactorViewController(title: String) -> UIAlertController {
let pending = UIAlertController(title: "", message: nil, preferredStyle: .alert)
let heightConstraint:NSLayoutConstraint = NSLayoutConstraint(item: pending.view, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: self.view.frame.height * 0.10)
pending.view.addConstraint(heightConstraint)
let label = UILabel()
label.text = title
label.textColor = UIColor.black
label.sizeToFit()
let space = UIView(frame: CGRect(x: 0, y: 0, width: 8, height: 8))
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.isUserInteractionEnabled = false
indicator.startAnimating()
let width = Int(label.frame.size.width + indicator.frame.size.width + space.frame.size.width)
let view = UIStackView(arrangedSubviews: [indicator, space, label])
view.axis = .horizontal
view.frame = CGRect(x: 20, y: 0, width: width, height: Int(heightConstraint.constant))
pending.view.addSubview(view)
let widthConstraint:NSLayoutConstraint = NSLayoutConstraint(item: pending.view, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.greaterThanOrEqual, toItem: view, attribute: NSLayoutAttribute.width, multiplier: 1, constant: CGFloat(width))
pending.view.addConstraint(widthConstraint)
self.present(pending, animated: true, completion: nil)
return pending
}
Well try this code.
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil
message:#"Creating new user\n\n\n"
preferredStyle:UIAlertControllerStyleAlert];
UIActivityIndicatorView *loader = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
loader.center = CGPointMake(130.5, 65.5);
loader.color = [UIColor blackColor];
[loader startAnimating];
[alert.view loader];
[self presentViewController:alert animated:NO completion:nil];
Try this:
activityView.center = CGPointMake(self.view.bounds.size.width/2.0, self.view.bounds.size.height / 2.0)
Also you will need to check for landscape mode and reverse width and height.
if(landscapeMode)activityView.center = CGPointMake(self.view.bounds.size.height/2.0, self.view.bounds.size.width / 2.0)
Maybe you can get the alert view position?
alert.view.frame.origin.x
alert.view.frame.origin.y
and use that to place your activity view dynamically ie with the variables?
Of course you might also want to get the size divide by 2 and add that so that its centred as well.
alert.view.frame.size.height
alert.view.frame.size.width
I had the same problem and using frame positioning didn't work for me.
Yimin Lin's answer was very close for me, but I just wanted to present an alternative using constraints in non-visual format:
//...
indicator.setTranslatesAutoresizingMaskIntoConstraints(false)
alert.view.addSubview(indicator)
alert.view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: alert.view, attribute: attribute: NSLayoutAttribute.CenterX, multiplier: 1, constant: 0))
alert.view.addConstraint(NSLayoutConstraint(item: indicator, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: alert.view, attribute: attribute: NSLayoutAttribute.CenterY, multiplier: 1, constant: 0))
//...

Resources