Why is a UIButton consuming touches but not a UIControl - ios

I have some custom buttons in a table view cell.
These buttons are contained by another view which does not take up the whole cell.
I want the buttons to always respond to taps (and consume the tap so that the cell is not selected at the same time).
I want my button container view to consume taps that are not on the buttons themselves (so that the cell is not selected).
I want anywhere in the cell outside my buttons container to select the cell as per usual.
To this end, I have attached a gesture recogniser to my buttons container view.
This has the desired effect, as long as my buttons are UIButtons (ie tapping the button itself cause a TouchUpInside event on the button, tapping anywhere else in the buttons container does nothing and tapping anywhere else in the cell, outside the buttons container, causes the cell to be selected). However, if I use a UIControl instead of a UIButton then this is no longer the case — the control never responds to tapping (the buttons container always consumes the tap and tapping outside the buttons container, in the cell, causes the cell to be selected). It should be noted that if I do not add a gesture recogniser to my buttons container then the control responds to taps in the same way as a UIButton.
My only explanation is that a UIButton (which inherits from UIControl) somehow adds some extra touch handling. In which case I would like to know what it does and how I should emulate it (I need to use a UIControl instead of a UIButton because my button has a custom view hierarchy for which I do not want to play around in the UIButton).
The code below for a view controller should allow anyone to reproduce the problem:
class ViewController: UITableViewController, UIGestureRecognizerDelegate {
lazy var containerView: UIView = {
let view: UIView = UIView()
view.backgroundColor = UIColor.redColor()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(self.buttonContainerView)
view.addConstraints([
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.TrailingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.buttonContainerView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0)
])
return view
}()
lazy var buttonContainerView: UIView = {
let view: UIView = UIView()
view.backgroundColor = UIColor.blueColor()
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(self.control)
view.addSubview(self.button)
view.addConstraints([
NSLayoutConstraint(item: self.control, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 0.5, constant: 0.0),
NSLayoutConstraint(item: self.control, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 1.5, constant: 0.0),
NSLayoutConstraint(item: self.button, attribute: NSLayoutAttribute.CenterY, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterY, multiplier: 1.0, constant: 0.0)
])
return view
}()
lazy var control: UIControl = {
let view: UIControl = TestControl(frame: CGRectZero)
view.addTarget(self, action: Selector("controlTapped:"), forControlEvents: UIControlEvents.TouchUpInside)
return view
}()
lazy var button: UIButton = {
let view: UIButton = UIButton.buttonWithType(UIButtonType.Custom) as! UIButton
view.setTitle("Tap button", forState: UIControlState.Normal)
view.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addTarget(self, action: Selector("buttonTapped:"), forControlEvents: UIControlEvents.TouchUpInside)
return view
}()
func controlTapped(sender: UIControl) -> Void {
println("Control tapped!")
}
func buttonTapped(sender: UIButton) -> Void {
println("Button tapped!")
}
var recogniser: UITapGestureRecognizer?
var blocker: UITapGestureRecognizer?
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = 200.0
self.containerView.layoutMargins = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
let recogniser: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tappedContainer:"))
recogniser.delegate = self
self.recogniser = recogniser
self.containerView.addGestureRecognizer(recogniser)
let blocker: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tappedBlocker:"))
blocker.delegate = self
self.blocker = blocker
self.buttonContainerView.addGestureRecognizer(blocker)
}
func tappedContainer(recogniser: UIGestureRecognizer) -> Void {
println("Tapped container!")
}
func tappedBlocker(recogniser: UIGestureRecognizer) -> Void {
println("Tapped blocker!")
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier: String = "identifier"
let cell: UITableViewCell
if let queuedCell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(identifier) as? UITableViewCell {
cell = queuedCell
}
else {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: identifier)
cell.contentView.layoutMargins = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
cell.contentView.backgroundColor = UIColor.purpleColor()
cell.contentView.addSubview(self.containerView)
cell.contentView.addConstraints([
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.TrailingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: self.containerView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: cell.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0)
])
}
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
println("selected cell")
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
}
class TestControl: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
let view: UIControl = self
let label: UILabel = UILabel()
label.text = "Tap control"
label.userInteractionEnabled = false
view.layer.borderColor = UIColor.orangeColor().CGColor
view.layer.borderWidth = 2.0
view.setTranslatesAutoresizingMaskIntoConstraints(false)
label.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(label)
view.addConstraints([
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 5.0),
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.CenterX, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.CenterX, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.GreaterThanOrEqual, toItem: view, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0),
NSLayoutConstraint(item: label, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.LessThanOrEqual, toItem: view, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0)
])
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
EDIT
To be clear, I am not looking for an alternative solution which 'just works' — I want to understand what this difference is and what I should be doing to emulate it, or potentially another semantically correct way.

My only explanation is that a UIButton (which inherits from UIControl) somehow adds some extra touch handling.
You are correct that UIButton is special. I did some research while answering a related question a while back and the reason why the button's event fires is mentioned in the Event Handling Guide for iOS: Gesture Recognizers' "Interacting with Other User Interface Controls" section:
In iOS 6.0 and later, default control actions prevent overlapping gesture recognizer behavior. For example, the default action for a button is a single tap. If you have a single tap gesture recognizer attached to a button’s parent view, and the user taps the button, then the button’s action method receives the touch event instead of the gesture recognizer.
It then lists examples and a single finger single tap on a UIButton is one of them.
The way to block gesture recognizers like the default controls do would be to have TestControl override gestureRecognizerShouldBegin: (see UIView Class Reference). If you wanted to mimic UIButton's behavior, you could use something like:
override func gestureRecognizerShouldBegin(gestureRecognizer: UIGestureRecognizer) -> Bool {
if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer {
if tapGestureRecognizer.numberOfTapsRequired == 1 && tapGestureRecognizer.numberOfTouchesRequired == 1 {
return false;
}
}
return true;
}

Related

UITableView Header section height is not adjusted automatically for a Custom UIView() class

I am having UIView() class where I am adding a label programatically and also given constraints to automatically adjust height of view based on content. I have used this class as HeaderView for a UItableView section. But the problem here is the height of this view is not adjusting accordingly to its content.
Here he is my code of that custom View.
class DynamicHeaderView: UIView {
override func draw(_ rect: CGRect) {
let headerLabel = UILabel()
headerLabel.numberOfLines = 0
headerLabel.sizeToFit()
headerLabel.text = "This is header view. It is dynamicaaly growing text and will automaticaly get adjusted to it"
self.backgroundColor = .green
headerLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerLabel)
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 16))
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -16))
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10))
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: -10))
} }
Code that I have written in my viewController,
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.countriesTable.sectionHeaderHeight = UITableView.automaticDimension;
self.countriesTable.estimatedSectionHeaderHeight = 25 }
func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let headerView = DynamicHeaderView()
return headerView
}
The height is always stick to the estimated header height as 25 which i have given in viewDidLoad() function.
You need to subclass UITableViewHeaderFooterView , then register the tableView with it , finally implement viewForHeaderInSection
class DynamicHeaderView: UITableViewHeaderFooterView {
let headerLabel = UILabel()
override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
headerLabel.numberOfLines = 0
// headerLabel.sizeToFit()
headerLabel.text = "This is header view. It is dynamicaaly growing text and will automaticaly get adjusted to it"
self.backgroundColor = .green
headerLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(headerLabel)
headerLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: .vertical)
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 16))
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .trailing, relatedBy: .equal, toItem: self, attribute: .trailing, multiplier: 1, constant: -16))
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 10))
self.addConstraint(NSLayoutConstraint(item: headerLabel, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 10))
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
//
In viewDidLoad
tableView.register(DynamicHeaderView.self, forHeaderFooterViewReuseIdentifier: "celld")
tableView.estimatedSectionHeaderHeight = 50
tableView.sectionHeaderHeight = UITableView.automaticDimension
//
func tableView(_ tableView: UITableView,
viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "celld") as! DynamicHeaderView
headerView.headerLabel.text = "heightForHeaderInSectionheightForHeaderInSectionheightForHeaderInSectionheightForHeaderInSectionheightForHeaderInSectionheightForHeaderInSection"
return headerView
}

Constraints programmatically for custom UIView in Dynamic ViewController

I'm creating new UIViewController dynamycally using this code
#IBAction func newVCBtnPressed(_ sender: Any) {
let controller = DynamicVC()
show(controller, sender: sender)
}
In the new UIViewController I'm using this code for creation of the new UIView:
override func loadView() {
view = UIView()
view.backgroundColor = .lightGray
}
In result I have view with .lightGray backgroundcolor.
I want to add custom UIView and setup the constraints programmatically, and in result i want UIView with following constraints:
top: 0
bottom:(view.frame.height*0.9)
leading:0
trailing:(view.frame.width*0.15)
width:(view.frame.width*0.85)
height:(view.frame.height*0.1)
Example:
Here is my code:
topMenuView = UIView()
topMenuView.backgroundColor = .red
view.addSubview(topMenuView)
topMenuView.translatesAutoresizingMaskIntoConstraints = false
setupConstraints(item: topMenuView, topC: 0, topToItem: view, bottomC: (view.frame.height*0.9), bottomToItem: view, widthC: (view.frame.width*0.85), heightC: (view.frame.height*0.1), leadingCon: 0, trailingCon: (view.frame.width*0.15))
I'm using this constructed function for constraints:
func setupConstraints(item:UIView, topC:CGFloat, topToItem:UIView, bottomC:CGFloat, bottomToItem:UIView, widthC:CGFloat, heightC:CGFloat, leadingCon:CGFloat, trailingCon:CGFloat) {
let topConstraint = NSLayoutConstraint(item: item, attribute: .top, relatedBy: .equal, toItem: topToItem, attribute: .bottom, multiplier: 1, constant: topC)
let bottomConstraint = NSLayoutConstraint(item: item, attribute: .bottom, relatedBy: .equal, toItem: bottomToItem, attribute: .top, multiplier: 1, constant: bottomC)
let widthConstraint = NSLayoutConstraint(item: item, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: widthC)
let heightConstraint = NSLayoutConstraint(item: item, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: heightC)
let leading = NSLayoutConstraint(item: item,attribute: .leading,relatedBy: .equal, toItem: view, attribute: .leadingMargin, multiplier: 1.0, constant: leadingCon)
let trailing = NSLayoutConstraint(item: item,attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailingMargin,multiplier: 1.0,constant: trailingCon)
view?.addConstraints([topConstraint, bottomConstraint, widthConstraint, heightConstraint, leading, trailing])
NSLayoutConstraint.activate([topConstraint, bottomConstraint, widthConstraint, heightConstraint, leading, trailing])
}
But in the result i receive only UIView with gray background, the new UIView with red background doesn't appears.
What I'm doing wrong???
You should only specify bottom OR height and width OR trailing, otherwise you are going to get conflicts here.
see playground:
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let red = UIView()
red.backgroundColor = .red
view.addSubview(red)
red.translatesAutoresizingMaskIntoConstraints = false
red.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
red.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
red.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.85).isActive = true
red.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1).isActive = true
}
}
PlaygroundPage.current.liveView = ViewController()

App is crashing on NSLayoutConstraint when added programmatically

I am getting the following error :
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView(UIConstraintBasedLayout)
Basically I want to have a blue view that is 200h x 200w that is centered in the middle of the screen.
UIViewController.swift
override func loadView() {
super.loadView()
self.view = View()
}
Meanwhile in the View.swift
class View: UIView {
var blueView : UIView?
convenience init() {
// also tried UIScreen.mainScreen().bounds
self.init(frame:CGRectZero)
self.backgroundColor = UIColor.redColor()
self.setupView()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init:coder")
}
override init(frame: CGRect) {
super.init(frame: frame)
}
func setupView() {
self.blueView = UIView()
self.blueView?.backgroundColor = UIColor.blueColor()
self.blueView?.translatesAutoresizingMaskIntoConstraints = false
// self.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.blueView!)
let centerXConstraint = NSLayoutConstraint(
// object that we want to constrain
item: self.blueView!,
// the attribute of the item we want to constraint
attribute: NSLayoutAttribute.CenterX,
// how we want to relate this item with another item so most likely its parent view
relatedBy: NSLayoutRelation.Equal,
// this is the item that we are setting up the relationship with
toItem: self,
attribute: NSLayoutAttribute.CenterX,
// How much I want the CenterX of BlueView to Differ from the CenterX of the self
multiplier: 1.0,
constant: 0)
let centerYConstraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.CenterY,
relatedBy: NSLayoutRelation.Equal,
toItem: self,
attribute: NSLayoutAttribute.CenterY,
multiplier: 1.0,
constant: 0)
// These work but the previous two don't
let widthContraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.Width,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0,
constant: 200)
let heightConstraint = NSLayoutConstraint(
item: self.blueView!,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: nil,
attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0,
constant: 200)
self.blueView?.addConstraints([widthContraint, heightConstraint, centerXConstraint, centerYConstraint])
}
Height and width constraints belong to the view they pertain to. Centering and positioning belong to that view's parent so you must add these two to the parent, not the view itself.

iOS / Swift Can't add gesture to child class

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.

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