iOS / Swift Can't add gesture to child class - ios

I'm writing an child class but couldn't add gesture recognize for it.
Selector error. Here is my code:
import UIKit
class Alert: KeyboardViewController {
var AlertContainer = UIView()
func ShowAlert(message: String,view: UIView)
{
var tap = UITapGestureRecognizer(target: self, action:Selector("CloseAlertView"))
tap.numberOfTapsRequired = 1
AlertContainer.addGestureRecognizer(tap)
AlertContainer.setTranslatesAutoresizingMaskIntoConstraints(false)
AlertContainer.userInteractionEnabled = true
AlertContainer.backgroundColor = UIColor(rgb: 0x000000, alpha: 0.8)
view.addSubview(AlertContainer)
var CTop = NSLayoutConstraint(item: AlertContainer, attribute: .Top, relatedBy: .Equal, toItem: view, attribute: .Top, multiplier: 1.0, constant: 0.0)
var CRight = NSLayoutConstraint(item: AlertContainer, attribute: .Right, relatedBy: .Equal, toItem: view, attribute: .Right, multiplier: 1.0, constant: 0.0)
var CLeft = NSLayoutConstraint(item: AlertContainer, attribute: .Left, relatedBy: .Equal, toItem: view, attribute: .Left, multiplier: 1.0, constant: 0.0)
var CBottom = NSLayoutConstraint(item: AlertContainer, attribute: .Bottom, relatedBy: .Equal, toItem: view, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
view.addConstraints([CTop,CRight,CLeft,CBottom])
}
func CloseAlertView() {
AlertContainer.removeFromSuperview()
}
}
But when i tap on to this view, error: exc_bad_access or some time unrecognized selector sent to instance. Really don't know why ?
and one more question that i can't access to parent class view by super.view ?

Are you are calling this class in the following way (if not ignore my answer)?
// wrong implementation
func myFunc() {
var a = Alert()
a.ShowAlert("Message", view: view)
}
The previous code is wrong (and causes BAD_ACCESS) because a does not exists anymore after the function finished. So when the gesture is fired, it acts on nothing and crashes.
Solution:
var a = Alert() // a property of the caller class
func myFunc() {
a.ShowAlert("Message", view: view)
}
In this way the life of a is long enough to wait for the gesture.

Related

NSLayoutConstraint in playground

I'm trying to reproduce a scenario like this where the red and blue rectangles can occupy same width and height (and same gap between them) for different screen sizes.
I'm using NSLayoutConstraint (I know that anchors are preferred now, just trying to explore the basics). I tried the following code in swift playground:
import Foundation
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
var firstColorView: UIView!
var secondColorView: UIView!
override func viewWillAppear(_ animated: Bool) {
var myView: UIView!
myView = view
myView.backgroundColor = .white
firstColorView = UIView()
secondColorView = UIView()
firstColorView.backgroundColor = .red
secondColorView.backgroundColor = .blue
myView.addSubview(firstColorView)
myView.addSubview(secondColorView)
//myView.translatesAutoresizingMaskIntoConstraints = false
//view.addSubview(myView)
// horizontal constraints
let left_constraint = NSLayoutConstraint(item: firstColorView, attribute: .leftMargin, relatedBy: .equal, toItem: myView, attribute: .left, multiplier: 1.0, constant: 20)
let middle_constraint = NSLayoutConstraint(item: secondColorView, attribute: .leftMargin, relatedBy: .equal, toItem: firstColorView, attribute: .right, multiplier: 1.0, constant: 10)
let right_constraint = NSLayoutConstraint(item: myView, attribute: .rightMargin, relatedBy: .equal, toItem: secondColorView, attribute: .right, multiplier: 1.0, constant: 20)
let width_constraint = NSLayoutConstraint(item: firstColorView, attribute: .width, relatedBy: .equal, toItem: secondColorView, attribute: .width, multiplier: 1.0, constant: 0)
// vertical constraints
let top_constraint1 = NSLayoutConstraint(item: firstColorView, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1.0, constant: 10)
let top_constraint2 = NSLayoutConstraint(item: secondColorView, attribute: .top, relatedBy: .equal, toItem: myView, attribute: .top, multiplier: 1.0, constant: 10)
let bottom_constraint1 = NSLayoutConstraint(item: myView, attribute: .bottom, relatedBy: .equal, toItem: firstColorView, attribute: .bottom, multiplier: 1.0, constant: 10)
let bottom_constraint2 = NSLayoutConstraint(item: myView, attribute: .bottom, relatedBy: .equal, toItem: secondColorView, attribute: .bottom, multiplier: 1.0, constant: 0)
NSLayoutConstraint.activate([left_constraint, middle_constraint, right_constraint, width_constraint, top_constraint1, top_constraint2, bottom_constraint1, bottom_constraint2])
self.view.layoutIfNeeded()
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = MyViewController()
But all it shows is a white screen, whose width doesn't match an iphone's. What am I doing wrong here? Why can't I see the red and blue screen?
You miss
firstColorView.translatesAutoresizingMaskIntoConstraints = false
secondColorView.translatesAutoresizingMaskIntoConstraints = false

SideMenu with AutoLayout

I'am trying to make a side menu and i have some problems with setting it with auto layout.
I have a rootViewController that i add to it the leftMenuVC as childVC then i set the constraints.
class RootVC: UIViewController, NavigationBarDelegate {
var leftMenuVC: UIViewController?
var navigationBar = NavigationBar()
var isMenuCollapsed = true
override func viewDidLoad() {
leftMenuVC = leftVC()
addChildViewController(leftMenuVC!)
view.addSubview(leftMenuVC!.view)
leftMenuVC!.didMove(toParentViewController: self)
}
override func viewDidLayoutSubviews() {
if let v = leftMenuVC?.view {
v.translatesAutoresizingMaskIntoConstraints = false
v.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
v.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
v.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -140).isActive = true
v.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
}
}
func menuButtonClicked(){
}
}
So my question is how to change constraints to hide/show the menu with support of orientations
What I usually do when I want to hide a view outside the screen with constraints is:
1 Set all constraints so that the sideview is visible (in active state)
2 Keep in reference the constraint that stick your sideview on one side (here the left one)
leftAnchor = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
view.addConstraint(leftAnchor)
view.addConstraint(NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 0.6, constant: 0))
3 Set one more constraint so that the view will be hidden. Usually it's something like that. Note that the priority is set to 999 to avoid constraint conflicts.
var hiddingConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
hiddingConstraint.priority = 999
view.addConstraint(hiddingConstraint)
4 Animate by activating or not your leftAnchor
UIView.animate(withDuration: 0.3) {
self.leftAnchor.active = false
self.view.layoutIfNeeded()
}
So you should end up with a code like this:
class RootVC: UIViewController, NavigationBarDelegate {
var leftMenuVC: UIViewController?
var navigationBar = NavigationBar()
var isMenuCollapsed = true {
didSet {
UIView.animate(withDuration: 0.3) {
self.leftAnchor?.isActive = self.isMenuCollapsed
self.view.layoutIfNeeded()
}
}
}
var leftAnchor : NSLayoutConstraint?
override func viewDidLoad() {
leftMenuVC = leftVC()
addChildViewController(leftMenuVC!)
view.addSubview(leftMenuVC!.view)
leftMenuVC!.didMove(toParentViewController: self)
}
override func viewDidLayoutSubviews() {
if let v = leftMenuVC?.view {
leftAnchor = NSLayoutConstraint(item: v, attribute: .left, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
view.addConstraint(leftAnchor!)
view.addConstraint(NSLayoutConstraint(item: v, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0))
view.addConstraint(NSLayoutConstraint(item: v, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 0.6, constant: 0))
var hiddingConstraint = NSLayoutConstraint(item: v, attribute: .right, relatedBy: .equal, toItem: view, attribute: .left, multiplier: 1, constant: 0)
hiddingConstraint.priority = 999
view.addConstraint(hiddingConstraint)
}
}
func menuButtonClicked(){
isMenuCollapsed = !isMenuCollapsed
}
}
PS: I won't put the constraints setting in viewDidLayoutSubviews, maybe in viewWillAppear, as you don't have to set them every time the device is being rotated. That's the purpose of constraints
Instead of writing this code by yourself, save yourself the trouble.
Here is MMDrawerController to your rescue. I am using it myself. It's super easy to implement and offers lots of customization options. Hope you find it useful. :-)

How to update constraints dynamically?

I have three UIButton, I have created programmatically constraints, at some condition i am removing one of UIButton as button.removeFromSuperview() & rest of two button will set constraints as per priorities.
Issue is when i am removing one UIButton(buttonWink) then as of viewLifeCycle viewWillLayoutSubviews will called & App crashes in below line
buttonWink.translatesAutoresizingMaskIntoConstraints = false
Ofcourse because buttonWink is removed from superview however we can check before setting constraints as
if buttonWink != nil {
buttonWink.translatesAutoresizingMaskIntoConstraints = false
}
But by checking nil for every button will make code lengthy, is there any way of doing the same? i will really appreciate friends.
Output -
Here i am attaching my tried full code.
import UIKit
class MasterViewController: UIViewController {
#IBOutlet weak var buttonMessage : UIButton!
#IBOutlet weak var buttonLike : UIButton!
#IBOutlet weak var buttonWink : UIButton!
#IBAction func tapsOnLike(_ sender: UIButton) {
sender.removeFromSuperview()
}
#IBAction func tapsOnWink(_ sender: UIButton) {
sender.removeFromSuperview()
}
#IBAction func tapsOnNextButton(){
let vc = DetailViewController(nibName: "DetailViewController", bundle: nil)
navigationController?.pushViewController(vc, animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
setConstraints()
navigationController?.isNavigationBarHidden = true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
// setConstraints()
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
//setConstraints()
}
func setConstraints() {
if buttonMessage != nil {
buttonMessage?.translatesAutoresizingMaskIntoConstraints = false
}
buttonLike?.translatesAutoresizingMaskIntoConstraints = false
buttonWink?.translatesAutoresizingMaskIntoConstraints = false
//Constraints for Message button
let leading = NSLayoutConstraint(item: buttonMessage, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 8)
leading.isActive = true
let trailingToSuperView = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingToSuperView.priority = 998
trailingToSuperView.isActive = true
let bottomToSuperView = NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: -8)
bottomToSuperView.isActive = true
let trailingToWink = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: buttonWink, attribute: .leading, multiplier: 1, constant: -8)
trailingToWink.priority = 999
trailingToWink.isActive = true
let leadingToLike = NSLayoutConstraint(item: buttonMessage, attribute: .trailing, relatedBy: .equal, toItem: buttonLike, attribute: .leading, multiplier: 1.0, constant: -8)
leadingToLike.isActive = true
let alignBottomToWink = NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: buttonWink, attribute: .bottom, multiplier: 1, constant: 0)
alignBottomToWink.isActive = true
let alignBottomToLike = NSLayoutConstraint(item: buttonMessage, attribute: .bottom, relatedBy: .equal, toItem: buttonLike, attribute: .bottom, multiplier: 1, constant: 0)
alignBottomToLike.isActive = true
let equalWidthToWink = NSLayoutConstraint(item: buttonMessage, attribute: .width, relatedBy: .equal, toItem: buttonWink, attribute: .width, multiplier: 1, constant: 0)
equalWidthToWink.isActive = true
let equalWidthToLike = NSLayoutConstraint(item: buttonMessage, attribute: .width, relatedBy: .equal, toItem: buttonLike, attribute: .width, multiplier: 1, constant: 0)
equalWidthToLike.isActive = true
//Constraints for like button
let trailingLikeToSuperView = NSLayoutConstraint(item: buttonLike, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingLikeToSuperView.priority = 999
trailingLikeToSuperView.isActive = true
let leadingToWink = NSLayoutConstraint(item: buttonLike, attribute: .trailing, relatedBy: .equal, toItem: buttonWink, attribute: .leading, multiplier: 1.0, constant: -8)
leadingToWink.isActive = true
//Constraints for Wink button
let trailingWinkToSuperView = NSLayoutConstraint(item: buttonWink, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingWinkToSuperView.isActive = true
}
}
Just FYI -
simply use a stack view for this.
It is incredibly easy: it's why Apple finally added a stack concept a couple yrs ago.
I just added UIStackView & it's too easy,
Three buttons are added in horizontal stack view with 8 points spacing & added three constraints to stack view as leading, trailing & bottom to superview
#IBOutlet weak var stackView: UIStackView!
#IBAction func tapsOnLikeInStack(_ sender: UIButton) {
sender.isHidden = true
}
#IBAction func tapsOnWinkInStack(_ sender: UIButton) {
sender.isHidden = true
}
func constraintsForStackView(){
stackView?.translatesAutoresizingMaskIntoConstraints = false
stackView.spacing = 8
stackView.axis = .horizontal
stackView.distribution = .fillEqually
stackView.alignment = .fill
let leading = NSLayoutConstraint(item: stackView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1.0, constant: 8)
leading.isActive = true
let trailingToSuperView = NSLayoutConstraint(item: stackView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: -8)
trailingToSuperView.isActive = true
let bottomToSuperView = NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: -120)
bottomToSuperView.isActive = true
}
as far as your viewWillLayoutSubviews code put your buttons into an array to use:
for btn in buttons {
if (btn.superview != nil) {
btn.translatesAutoresizingMaskIntoConstraints = false
}
}
and if you want to change the constraints for the buttons still in the view, give them identifiers when you create the constraints:
for btn in buttons {
if btn.superview != nil {
btn.translatesAutoresizingMaskIntoConstraints = false
for constraint in btn.constraints {
switch constraint.identifier {
case "winkBtnTrailing":
//do stuff like constraint = new layout constraint
//or constraint.firstItem = *new first Item to base trailing on*
break
default:
//don't do stuff?
break
}
}
}
}
Just as a FYI using button.removeFromSuperview() doesn't set that button to nil, it just removes it from the view you called .addSubview(button) on

Is iOS UILabel constraint from code with margin broken?

I'm trying to implement a popup layout like following:
This works fine in a storyboard with margins and everything. In storyboard it looks like this:
But if I make the same constraint in code I get this result:
The label has a light blue background and the view the label is inside has the dark blue background. The popup background has a border around itself. So basically the popup matches the child but the label inside the child overflows parent and grand parent BUT only because it has margins... If I remove margins it goes right to the border!
I've tryed making the exact same constraint just in code. I'm very open for alternative suggestions involving automatic adjusting width.
My code for creating popup:
func showPopup(caller: UIView) {
closePopups()
// setup view
currentPopup = UIView()
self.view.addSubview(currentPopup)
currentPopup.backgroundColor = UIColorFromHex(Constants.Colors.white, alpha: 1)
// setup constraints
currentPopup.translatesAutoresizingMaskIntoConstraints = false
// top constraint
let topSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Top, relatedBy: .Equal, toItem: intoWordsBar.view, attribute: .Bottom, multiplier: 1.0, constant: 0)
self.view.addConstraint(topSideConstraint)
// setup child elements
var children = [PopupChildButton]()
let childOne = createChild("writing_strategy_1", parent: currentPopup, aboveChild: nil, hasBorder: true, feature: FeatureManager.BarFeature.WriteReadLetterName)
children.append(childOne)
let childTwo = createChild("writing_strategy_2", parent: currentPopup, aboveChild: children[0], hasBorder: true, feature: FeatureManager.BarFeature.WriteReadLetterSound)
children.append(childTwo)
let childThree = createChild("writing_strategy_3", parent: currentPopup, aboveChild: children[1], hasBorder: true, feature: FeatureManager.BarFeature.WriteReadWord)
children.append(childThree)
let childFour = createChild("writing_strategy_4", parent: currentPopup, aboveChild: children[2], hasBorder: false, feature: FeatureManager.BarFeature.WriteReadSentence)
children.append(childFour)
let parentSize = getWidth(caller)
//TODO MARK: <-- here working, need to add toggle function and graphics to childrens, documentation on methods, move to constructor class?
// setup rest of constraints
// add bottom constraint, equal to bottom of last child
let bottomSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Bottom, relatedBy: .Equal, toItem: children[children.count-1], attribute: .Bottom, multiplier: 1.0, constant: 0)
self.view.addConstraint(bottomSideConstraint)
// left constraint
let leftSideConstraint = NSLayoutConstraint(item: currentPopup, attribute: .Left, relatedBy: .Equal, toItem: caller, attribute: .Right, multiplier: 1.0, constant: (-parentSize)/2)
self.view.addConstraint(leftSideConstraint)
// add border
currentPopup.addBorder(edges: [.All], colour: UIColorFromHex(Constants.Colors.dark_grey, alpha: 1), thickness: 1)
//TODO <-- last piece
//childOne.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childTwo.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childThree.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
//childFour.addTarget(self, action: #selector(KeyboardViewController.childClick(_:)), forControlEvents: .TouchUpInside)
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
My code for creating child:
func createChild(text: String, parent: UIView, aboveChild: UIView?, hasBorder: Bool, feature: FeatureManager.BarFeature) -> PopupChildButton {
// setup child element
let childBtn = PopupChildButton()
childBtn.setRelatedFeature(feature)
// set the right background color
if intoWordsBar.getFeatureManager().isFeatureActive(feature) {
childBtn.backgroundColor = UIColorFromHex(Constants.Colors.light_blue, alpha: 1)
//childBtn.setImage(UIImage(named: "Checkmark"))
} else {
childBtn.backgroundColor = UIColorAndAlphaFromHex(Constants.Colors.transparent)//TODO Highlight implementation needs to be optimized, icon should be moved all the way to the left... somehow //TODO Add new checkmark icon
//childBtn.setImage(nil)
}
childBtn.translatesAutoresizingMaskIntoConstraints = false
parent.addSubview(childBtn)
// add constraints
// top constraint
if let aboveChild = aboveChild {
let topSideConstraint = NSLayoutConstraint(item: childBtn, attribute: .Top, relatedBy: .Equal, toItem: aboveChild, attribute: .Bottom, multiplier: 1.0, constant: 0)
parent.addConstraint(topSideConstraint)
} else {
let topSideConstraint = NSLayoutConstraint(item: childBtn, attribute: .Top, relatedBy: .Equal, toItem: parent, attribute: .Top, multiplier: 1.0, constant: 0)
parent.addConstraint(topSideConstraint)
}
// height constraint
let heightConstraint = NSLayoutConstraint(item: childBtn, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: CGFloat(Constants.Sizes.popupChildHeight))
parent.addConstraint(heightConstraint)
// left constraint
let leftSideConstraint = NSLayoutConstraint(item: parent, attribute: .Leading, relatedBy: .Equal, toItem: childBtn, attribute: .Leading, multiplier: 1.0, constant: 0)
parent.addConstraint(leftSideConstraint)
// right constraint
let rightSideConstraint = NSLayoutConstraint(item: parent, attribute: .Trailing, relatedBy: .Equal, toItem: childBtn, attribute: .Trailing, multiplier: 1.0, constant: 0)
parent.addConstraint(rightSideConstraint)
// add border
if hasBorder {
childBtn.addBorder(edges: .Bottom, colour: UIColorFromHex(Constants.Colors.dark_grey, alpha: 1), thickness: 1)
}
// create grandchildren
let label = UILabel()
// setup looks
label.textColor = UIColorFromHex(Constants.Colors.black, alpha: 1)
label.textAlignment = .Center
childBtn.backgroundColor = UIColorFromHex(Constants.Colors.dark_blue, alpha: 1)
label.backgroundColor = UIColorFromHex(Constants.Colors.light_blue, alpha: 1)
label.text = text.localized
label.translatesAutoresizingMaskIntoConstraints = false
childBtn.addSubview(label)
// add constraints
// left constraint label
let leftLabelConstraint = NSLayoutConstraint(item: label, attribute: .Left, relatedBy: .Equal, toItem: childBtn, attribute: .Left, multiplier: 1.0, constant: CGFloat(Constants.Sizes.popupMargin))
childBtn.addConstraint(leftLabelConstraint)
// right constraint label
let rightLabelConstraint = NSLayoutConstraint(item: label, attribute: .Right, relatedBy: .Equal, toItem: childBtn, attribute: .Right, multiplier: 1.0, constant: CGFloat(Constants.Sizes.popupMargin))
childBtn.addConstraint(rightLabelConstraint)
// top constraint
let labelTopSideConstraint = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: childBtn, attribute: .Top, multiplier: 1.0, constant: 0)
childBtn.addConstraint(labelTopSideConstraint)
// bottom constraint
//let labelBottomSideConstraint = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: childBtn, attribute: .Bottom, multiplier: 1.0, constant: 0)
//childBtn.addConstraint(labelBottomSideConstraint)
return childBtn
}
No, it is not broken.
When defining trailing constraints you must set the parent view as the first item and the child view as the second item. This is in reversed order compared to a leading constraint.
I pulled to constraints from a storyboard to illustrate this. These constraints make sure the header has a 10px margin from leading and trailing of parent view.

Custom keyboard for iOS

I've built keyboard this way. (code below)
But, there are some problems like:
when you click on some button, it's click animation taking long to get back.
there is no way to place some common keys as globe symbol for language changing or caps lock
What I want to do is, to modify original iOS keyboard and add some other buttons.
Is it possible? Any suggestions?
import UIKit
class KeyboardViewController: UIInputViewController {
#IBOutlet var nextKeyboardButton: UIButton!
override func updateViewConstraints() {
super.updateViewConstraints()
// Add custom view sizing constraints here
}
override func viewDidLoad() {
super.viewDidLoad()
let buttonTitles1 = ["Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P"]
let buttonTitles2 = ["A", "S", "D", "F", "G", "H", "J", "K", "L"]
let buttonTitles3 = ["CP", "Z", "X", "C", "V", "B", "N", "M", "BP"]
let buttonTitles4 = ["CHG", "SPACE", "RETURN"]
var row1 = createRowOfButtons(buttonTitles1)
var row2 = createRowOfButtons(buttonTitles2)
var row3 = createRowOfButtons(buttonTitles3)
var row4 = createRowOfButtons(buttonTitles4)
self.view.addSubview(row1)
self.view.addSubview(row2)
self.view.addSubview(row3)
self.view.addSubview(row4)
row1.setTranslatesAutoresizingMaskIntoConstraints(false)
row2.setTranslatesAutoresizingMaskIntoConstraints(false)
row3.setTranslatesAutoresizingMaskIntoConstraints(false)
row4.setTranslatesAutoresizingMaskIntoConstraints(false)
addConstraintsToInputView(self.view, rowViews: [row1, row2, row3, row4])
}
func createRowOfButtons(buttonTitles: [NSString]) -> UIView {
var buttons = [UIButton]()
var keyboardRowView = UIView(frame: CGRectMake(0, 0, 320, 50))
for buttonTitle in buttonTitles{
let button = createButtonWithTitle(buttonTitle)
buttons.append(button)
keyboardRowView.addSubview(button)
}
addIndividualButtonConstraints(buttons, mainView: keyboardRowView)
return keyboardRowView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated
}
override func textWillChange(textInput: UITextInput) {
// The app is about to change the document's contents. Perform any preparation here.
}
override func textDidChange(textInput: UITextInput) {
// The app has just changed the document's contents, the document context has been updated.
var textColor: UIColor
var proxy = self.textDocumentProxy as UITextDocumentProxy
if proxy.keyboardAppearance == UIKeyboardAppearance.Dark {
textColor = UIColor.whiteColor()
} else {
textColor = UIColor.blackColor()
}
}
func createButtonWithTitle(title: String) -> UIButton {
let button = UIButton.buttonWithType(.System) as UIButton
button.frame = CGRectMake(0, 0, 20, 20)
button.setTitle(title, forState: .Normal)
button.sizeToFit()
button.titleLabel?.font = UIFont.systemFontOfSize(15)
button.setTranslatesAutoresizingMaskIntoConstraints(false)
button.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
button.setTitleColor(UIColor.darkGrayColor(), forState: .Normal)
button.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)
return button
}
func didTapButton(sender: AnyObject?) {
let button = sender as UIButton
var proxy = textDocumentProxy as UITextDocumentProxy
if let title = button.titleForState(.Normal) {
switch title {
case "BP" :
proxy.deleteBackward()
case "RETURN" :
proxy.insertText("\n")
case "SPACE" :
proxy.insertText(" ")
case "CHG" :
self.advanceToNextInputMode()
default :
proxy.insertText(title)
}
}
}
func addIndividualButtonConstraints(buttons: [UIButton], mainView: UIView){
for (index, button) in enumerate(buttons) {
var topConstraint = NSLayoutConstraint(item: button, attribute: .Top, relatedBy: .Equal, toItem: mainView, attribute: .Top, multiplier: 1.0, constant: 1)
var bottomConstraint = NSLayoutConstraint(item: button, attribute: .Bottom, relatedBy: .Equal, toItem: mainView, attribute: .Bottom, multiplier: 1.0, constant: -1)
var rightConstraint : NSLayoutConstraint!
if index == buttons.count - 1 {
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: mainView, attribute: .Right, multiplier: 1.0, constant: -1)
}else{
let nextButton = buttons[index+1]
rightConstraint = NSLayoutConstraint(item: button, attribute: .Right, relatedBy: .Equal, toItem: nextButton, attribute: .Left, multiplier: 1.0, constant: -1)
}
var leftConstraint : NSLayoutConstraint!
if index == 0 {
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: mainView, attribute: .Left, multiplier: 1.0, constant: 1)
}else{
let prevtButton = buttons[index-1]
leftConstraint = NSLayoutConstraint(item: button, attribute: .Left, relatedBy: .Equal, toItem: prevtButton, attribute: .Right, multiplier: 1.0, constant: 1)
let firstButton = buttons[0]
var widthConstraint = NSLayoutConstraint(item: firstButton, attribute: .Width, relatedBy: .Equal, toItem: button, attribute: .Width, multiplier: 1.0, constant: 0)
widthConstraint.priority = 800
mainView.addConstraint(widthConstraint)
}
mainView.addConstraints([topConstraint, bottomConstraint, rightConstraint, leftConstraint])
}
}
func addConstraintsToInputView(inputView: UIView, rowViews: [UIView]){
for (index, rowView) in enumerate(rowViews) {
var rightSideConstraint = NSLayoutConstraint(item: rowView, attribute: .Right, relatedBy: .Equal, toItem: inputView, attribute: .Right, multiplier: 1.0, constant: -1)
var leftConstraint = NSLayoutConstraint(item: rowView, attribute: .Left, relatedBy: .Equal, toItem: inputView, attribute: .Left, multiplier: 1.0, constant: 1)
inputView.addConstraints([leftConstraint, rightSideConstraint])
var topConstraint: NSLayoutConstraint
if index == 0 {
topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: inputView, attribute: .Top, multiplier: 1.0, constant: 0)
}else{
let prevRow = rowViews[index-1]
topConstraint = NSLayoutConstraint(item: rowView, attribute: .Top, relatedBy: .Equal, toItem: prevRow, attribute: .Bottom, multiplier: 1.0, constant: 0)
let firstRow = rowViews[0]
var heightConstraint = NSLayoutConstraint(item: firstRow, attribute: .Height, relatedBy: .Equal, toItem: rowView, attribute: .Height, multiplier: 1.0, constant: 0)
heightConstraint.priority = 800
inputView.addConstraint(heightConstraint)
}
inputView.addConstraint(topConstraint)
var bottomConstraint: NSLayoutConstraint
if index == rowViews.count - 1 {
bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: inputView, attribute: .Bottom, multiplier: 1.0, constant: 0)
}else{
let nextRow = rowViews[index+1]
bottomConstraint = NSLayoutConstraint(item: rowView, attribute: .Bottom, relatedBy: .Equal, toItem: nextRow, attribute: .Top, multiplier: 1.0, constant: 0)
}
inputView.addConstraint(bottomConstraint)
}
}
}
Use the github repo below. It's nearly 1:1 immitation of iOS 8 original keyboard and works like a charm
https://github.com/archagon/tasty-imitation-keyboard
when you click on some button, it's click animation taking long to get back.
Your keys are nothing more than UIButtons. The fade-out animation you're seeing is the normal animation for a UIButton—you can see it for yourself on most buttons across the system. Instead of initializing it with UIButtonType.System, use .Custom and set your own appearance for the desired states.
As an example (there are many possibilities):
let button = UIButton(.Custom)
button.setTitleColor(UIColor.redColor(), forState: .Highlighted)
Note that you don't even have to restrict yourself to using UIButton—custom keyboards are a blank slate.
there is no way to place some common keys as globe symbol for language changing or caps lock
From the Custom Keyboard Guide:
The system picks the appropriate “next” keyboard; there is no API to
obtain a list of enabled keyboards or for picking a particular
keyboard to switch to.
So these "special" keys are also up to you to provide, most likely with your own custom icons. If you're using UIButton, that probably means calling setImage:forState:. It looks like most third-party keyboards use a globe icon almost identicial to the system one for the "next" key.
There is no API to mod the system keyboard—you must build one yourself from the ground-up.
For slow animation, you can study these github repo on how they do their animation:
https://github.com/YuAo/WUEmoticonsKeyboard (See the popview when the button gets pressed.)
https://github.com/ayushgoel/AGEmojiKeyboard
https://github.com/kulpreetchilana/Custom-iOS-Keyboards
or search github:https://github.com/search?utf8=✓&q=iOS+keyboard
Note: They are mostly objective-c repo.
Lastly, it might be cheaper to install a custom keyboard than to make one:
http://www.imore.com/best-custom-keyboards-ios-8

Resources