How to animate keyboard scrolling with keyboardDismissMode = .interactive - ios

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.

Related

TextField bottom with iPhone X in Swift

I'm new in Swift, and I have an issue with the iPhone X.
I followed this tutorial: https://www.youtube.com/watch?v=FDay6ocBlnE&index=8&list=PL0dzCUj1L5JEfHqwjBV0XFb9qx9cGXwkq in order to create a Chat App.
My problem is that the textField is fixed to the bottom, and that is not good for the iPhone X.
I really don't know how I can change this, given that I'm more familiar with storyboard and here, the collectionViewController is entirely programmatically. I searched a lot of other tutorials but I found nothing to help.
This is my code:
The bottom view (with the textfield):
class ChatInputContainerView: UIView, UITextFieldDelegate {
// ...
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
// ...
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The CollectionViewController:
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// ...
lazy var inputContainerView: ChatInputContainerView = {
// I can't change the y value (it changes nothing)
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 54))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
}
Update
Here's the entire code:
import UIKit
import UserNotifications
class ChatLogController: UICollectionViewController, UITextFieldDelegate, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var user: Userm? {
didSet {
navigationItem.title = user?.username
loadMessages()
}
}
var messages = [Message]()
func loadMessages() {
guard let toId = user?.id else {
return
}
Api.Message.observeUserDiscussion(toId: toId) { (message) in
self.messages.append(message)
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
//scroll to the last index
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}
}
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
//Not authorised
UIApplication.shared.registerForRemoteNotifications()
}
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
collectionView?.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 20, right: 0)
// collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
collectionView?.alwaysBounceVertical = true
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ChatMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.keyboardDismissMode = .interactive
arrowBackButton(greyBack)
let image = UIImage(named: "iconProfilCog")
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(handleParamsMessage))
navigationItem.rightBarButtonItem?.tintColor = UIColor(red: 203/255, green: 203/255, blue: 203/255, alpha: 1)
setupKeyboardObservers()
emptyTextField()
}
func emptyTextField() {
self.inputContainerView.inputTextField.text = ""
self.inputContainerView.sendButton.isEnabled = false
self.inputContainerView.sendButton.alpha = 0.8
}
override func viewDidLayoutSubviews() {
inputContainerView.inputTextField.roundCorners([.topLeft,.bottomLeft], radius: 10)
inputContainerView.backgroundSendButtonView.roundCorners([.topRight,.bottomRight], radius: 22)
}
lazy var inputContainerView: ChatInputContainerView = {
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 54))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
func handleParamsMessage() {
print("params")
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let detailMessage = storyboard.instantiateViewController(withIdentifier: "MessageDetailTableViewController") as! MessageDetailTableViewController
if let user = user {
detailMessage.userId = user.id!
self.navigationController?.pushViewController(detailMessage, animated: true)
}
}
func handleUploadTap() {
let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = true
imagePickerController.delegate = self
//imagePickerController.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// if let videoUrl = info[UIImagePickerControllerMediaURL] as? URL {
// //we selected a video
// handleVideoSelectedForUrl(videoUrl)
// } else {
// //we selected an image
handleImageSelectedForInfo(info as [String : AnyObject])
// }
dismiss(animated: true, completion: nil)
}
fileprivate func handleImageSelectedForInfo(_ info: [String: AnyObject]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
HelperService.uploadMessagePictureToDatabase(selectedImage, completion: { (imageUrl) in
self.sendMessageWithImageUrl(imageUrl, image: selectedImage)
})
}
}
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
func setupKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func handleKeyboardDidShow() {
if messages.count > 0 {
let indexPath = IndexPath(item: messages.count - 1, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.chatLogController = self
let message = messages[indexPath.item]
cell.textView.text = message.text
setupCell(cell, message: message)
//lets modify the bubbleView's width somehow???
// cell.bubbleWidthAnchor?.constant = estimateFrameForText(message.text!).width + 25
if let text = message.text {
//a text message
cell.bubbleWidthAnchor?.constant = estimateFrameForText(text).width + 25
cell.textView.isHidden = false
} else if message.imageUrl != nil {
//fall in here if its an image message
cell.bubbleWidthAnchor?.constant = 200
cell.textView.isHidden = true
}
return cell
}
fileprivate func setupCell(_ cell: ChatMessageCell, message: Message) {
if let profileImageUrl = self.user?.profileImageUrl {
let photoUrl = URL(string: profileImageUrl)
cell.profileImageView.sd_setImage(with: photoUrl)
}
if message.fromId == Api.User.CURRENT_USER?.uid {
//outgoing blue
cell.bubbleView.backgroundColor = ChatMessageCell.blueColor
cell.textView.textColor = UIColor.white
cell.profileImageView.isHidden = true
cell.tailImageView.isHidden = true
cell.bubbleViewRightAnchor?.isActive = true
cell.bubbleViewLeftAnchor?.isActive = false
} else {
//incoming gray
cell.bubbleView.backgroundColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
cell.textView.textColor = UIColor(red: 70/255, green: 70/255, blue: 70/255, alpha: 1)
cell.profileImageView.isHidden = false
cell.tailImageView.isHidden = false
cell.bubbleViewRightAnchor?.isActive = false
cell.bubbleViewLeftAnchor?.isActive = true
}
if let messageImageUrl = message.imageUrl {
let photoUrl = URL(string: messageImageUrl)
cell.messageImageView.sd_setImage(with: photoUrl)
cell.messageImageView.isHidden = false
// cell.bubbleView.backgroundColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
} else {
cell.messageImageView.isHidden = true
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat = 80
let message = messages[indexPath.item]
if let text = message.text {
height = estimateFrameForText(text).height + 18
} else if let imageWidth = message.imageWidth?.floatValue, let imageHeight = message.imageHeight?.floatValue {
// h1 / w1 = h2 / w2
// solve for h1
// h1 = h2 / w2 * w1
height = CGFloat(imageHeight / imageWidth * 200)
}
let width = UIScreen.main.bounds.width
return CGSize(width: width, height: height)
}
fileprivate func estimateFrameForText(_ text: String) -> CGRect {
let size = CGSize(width: 200, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15, weight: .medium)], context: nil)
}
var containerViewBottomAnchor: NSLayoutConstraint?
func handleSend() {
self.inputContainerView.sendButton.isEnabled = false
let properties = ["text": inputContainerView.inputTextField.text!]
sendMessageWithPropertiesFIR(properties as [String : AnyObject])
}
fileprivate func sendMessageWithImageUrl(_ imageUrl: String, image: UIImage) {
let properties: [String: AnyObject] = ["imageUrl": imageUrl as AnyObject, "imageWidth": image.size.width as AnyObject, "imageHeight": image.size.height as AnyObject]
sendMessageWithPropertiesFIR(properties)
}
func sendMessageWithPropertiesFIR(_ properties: [String: AnyObject]) {
print(properties["text"])
var messageText = ""
if properties["text"] != nil {
messageText = properties["text"] as! String
} else {
messageText = "A envoyé une photo"
}
Api.Message.sendMessageWithProperties(toId: user!.id!, properties: properties) {
Api.Message.isUserMuted(userId: self.user!.id!, completion: { (isMuted) in
if !isMuted {
Api.UserToken.observeUserToken(withUser: self.user!.id!, completion: { (token) in
if let token = token {
Api.User.observeCurrentUser(completion: { (user) in
Api.Notification.sendNotifPush(token: token, message: "\(user.username!): \(messageText)")
})
}
})
}
})
self.emptyTextField()
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
handleSend()
return true
}
var startingFrame: CGRect?
var blackBackgroundView: UIView?
var startingImageView: UIImageView?
//my custom zooming logic
func performZoomInForStartingImageView(_ startingImageView: UIImageView) {
self.startingImageView = startingImageView
self.startingImageView?.isHidden = true
self.inputContainerView.inputTextField.resignFirstResponder()
startingFrame = startingImageView.superview?.convert(startingImageView.frame, to: nil)
let zoomingImageView = UIImageView(frame: startingFrame!)
zoomingImageView.backgroundColor = UIColor.red
zoomingImageView.image = startingImageView.image
zoomingImageView.isUserInteractionEnabled = true
zoomingImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleZoomOut)))
if let keyWindow = UIApplication.shared.keyWindow {
blackBackgroundView = UIView(frame: keyWindow.frame)
blackBackgroundView?.backgroundColor = UIColor.black
blackBackgroundView?.alpha = 0
keyWindow.addSubview(blackBackgroundView!)
keyWindow.addSubview(zoomingImageView)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// self.inputContainerView.inputTextField.resignFirstResponder()
self.blackBackgroundView?.alpha = 1
self.inputContainerView.alpha = 0
// math?
// h2 / w1 = h1 / w1
// h2 = h1 / w1 * w1
let height = self.startingFrame!.height / self.startingFrame!.width * keyWindow.frame.width
zoomingImageView.frame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
zoomingImageView.center = keyWindow.center
}, completion: { (completed) in
// do nothing
})
}
}
func handleZoomOut(_ tapGesture: UITapGestureRecognizer) {
if let zoomOutImageView = tapGesture.view {
//need to animate back out to controller
zoomOutImageView.layer.cornerRadius = 8
zoomOutImageView.clipsToBounds = true
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.startingFrame = self.startingImageView?.superview?.convert((self.startingImageView?.frame)!, to: nil)
zoomOutImageView.frame = self.startingFrame!
self.blackBackgroundView?.alpha = 0
self.inputContainerView.alpha = 1
}, completion: { (completed) in
zoomOutImageView.removeFromSuperview()
self.startingImageView?.isHidden = false
})
}
}
}
The View:
import UIKit
class ChatInputContainerView: UIView, UITextFieldDelegate {
weak var chatLogController: ChatLogController? {
didSet {
sendButton.addTarget(chatLogController, action: #selector(ChatLogController.handleSend), for: .touchUpInside)
uploadImageView.addGestureRecognizer(UITapGestureRecognizer(target: chatLogController, action: #selector(ChatLogController.handleUploadTap)))
inputTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
}
let inputColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
lazy var inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Entrer un message..."
textField.translatesAutoresizingMaskIntoConstraints = false
textField.delegate = self
textField.backgroundColor = inputColor
// textField.roundCorners([.topLeft,.bottomLeft], radius: 10)
textField.clipsToBounds = true
return textField
}()
let uploadImageView: UIImageView = {
let uploadImageView = UIImageView()
uploadImageView.isUserInteractionEnabled = true
uploadImageView.image = UIImage(named: "pinImage")
uploadImageView.translatesAutoresizingMaskIntoConstraints = false
return uploadImageView
}()
lazy var backgroundSendButtonView: UIView = {
let backgroundSendButtonView = UIView()
backgroundSendButtonView.backgroundColor = inputColor
backgroundSendButtonView.translatesAutoresizingMaskIntoConstraints = false
return backgroundSendButtonView
}()
let sendButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
addSubview(uploadImageView)
// sendButton.setTitle("Send", for: UIControlState())
sendButton.setImage(UIImage(named: "planeChat"), for: .normal)
sendButton.backgroundColor = UIColor.white
sendButton.tintColor = UIColor(red: 82/255, green: 121/255, blue: 179/255, alpha: 1)
sendButton.layer.cornerRadius = 20
sendButton.clipsToBounds = true
sendButton.translatesAutoresizingMaskIntoConstraints = false
//what is handleSend?
addSubview(sendButton)
addSubview(self.inputTextField)
//x,y,w,h
// A enlever après
self.inputTextField.leftAnchor.constraint(equalTo: uploadImageView.rightAnchor, constant: 12).isActive = true
//self.inputTextField.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
//self.inputTextField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
self.inputTextField.topAnchor.constraint(equalTo: topAnchor, constant: 4).isActive = true
self.inputTextField.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -4).isActive = true
self.inputTextField.heightAnchor.constraint(equalToConstant: 48).isActive = true
//x,y,w,h
sendButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
sendButton.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 38).isActive = true
sendButton.heightAnchor.constraint(equalToConstant: 38).isActive = true
//x,y,w,h
uploadImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 18).isActive = true
uploadImageView.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
uploadImageView.widthAnchor.constraint(equalToConstant: 18).isActive = true
uploadImageView.heightAnchor.constraint(equalToConstant: 20).isActive = true
//l//et backgroundSendButtonView = UIView()
//addSubview(backgroundSendButtonView)
// backgroundSendButtonView.roundCorners([.topRight,.bottomRight], radius: 24)
insertSubview(backgroundSendButtonView, belowSubview: sendButton)
backgroundSendButtonView.rightAnchor.constraint(equalTo: rightAnchor, constant: -4).isActive = true
backgroundSendButtonView.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
//backgroundSendButtonView.widthAnchor.constraint(equalToConstant: 30).isActive = true
backgroundSendButtonView.leftAnchor.constraint(equalTo: inputTextField.rightAnchor).isActive = true
backgroundSendButtonView.heightAnchor.constraint(equalTo: inputTextField.heightAnchor).isActive = true
//x,y,w,h
// let separatorLineView = UIView()
// separatorLineView.backgroundColor = UIColor(red: 220/255, green: 220/255, blue: 220/255, alpha: 1)
// separatorLineView.translatesAutoresizingMaskIntoConstraints = false
// addSubview(separatorLineView)
// //x,y,w,h
// separatorLineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
// separatorLineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
// separatorLineView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// separatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
let gradientView = UIView()
let colorTop = UIColor.clear.cgColor
let colorBottom = UIColor(red: 0, green: 0, blue: 0, alpha: 0.05).cgColor
gradientView.translatesAutoresizingMaskIntoConstraints = false
addSubview(gradientView)
//x,y,w,h
gradientView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
gradientView.topAnchor.constraint(equalTo: topAnchor, constant: -25).isActive = true
gradientView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
gradientView.heightAnchor.constraint(equalToConstant: 25).isActive = true
gradientView.backgroundColor = UIColor.clear
let gradientBackground = CAGradientLayer()
gradientBackground.colors = [ colorTop, colorBottom]
gradientBackground.locations = [0.0, 1.0]
var backgroundLayer = CALayer()
backgroundLayer = gradientBackground
let width = UIScreen.main.bounds.size.width
backgroundLayer.frame = CGRect(x: 0, y: 0, width: width, height: 25)
print(backgroundLayer.frame)
print(gradientView.bounds)
gradientView.layer.insertSublayer(backgroundLayer, at: 0)
}
func setGradient(_ view: UIView, colorTop: CGColor, colorBottom: CGColor) {
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
chatLogController?.handleSend()
return true
}
#objc func textFieldDidChange(_ textField: UITextField) {
if textField == self.inputTextField {
if self.inputTextField.text!.isEmpty {
disableButton()
} else {
// sendButton.setTitleColor(typoGreyButton, for: .normal)
self.sendButton.isEnabled = true
self.sendButton.alpha = 1
}
}
}
func disableButton(){
//sendButton.setTitleColor(smoothGray, for: .normal)
sendButton.isEnabled = false
self.sendButton.alpha = 0.8
}
func emptyTextField() {
self.inputTextField.text = ""
disableButton()
}
// func textViewDidChange(_ textView: UITextView) {
// print(textView)
// if textView == self.inputContainerView.inputTextField {
// if (self.inputContainerView.inputTextField.text?.isEmpty)! {
// disableButton()
// } else {
// // sendButton.setTitleColor(typoGreyButton, for: .normal)
// self.inputContainerView.sendButton.isEnabled = true
// }
// }
//
// }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you just want to anchor to the bottom safe area, you can do that anywhere in your view controller:
if #available(iOS 11.0, *) {
someView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
} else {
someView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
If, however, you want to use it as a constant (i.e. subtract the length of the bottom safe area from a value), you need to do that in a later lifecycle method like viewDidLayoutSubviews():
if #available(iOS 11.0, *) {
someView.bottomAnchor.constraint(equalTo: anotherView.bottomAnchor, constant: -view.safeAreaInsets.bottom).isActive = true
} else {
someView.bottomAnchor.constraint(equalTo: anotherView.bottomAnchor, constant: -bottomLayoutGuide.length).isActive = true
}
iOS 11 revamped their safe area API so make sure that you support pre-iOS-11 devices as I did in these examples.
I also just noticed that you've set the view's frame explicitly in its intializer. You typically don't want to set a view's frame like that if you're using auto layout (constraints). Therefore, I would suggest not setting the view's frame like you did and instead using constraints to do it.

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

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

UISegmentedControl with views animation code review please

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

Resources