How to properly add Key-Value Observer to my button? - ios

I have a UITableViewCell file and inside of it I do:
var followers: FollowersModel? {
didSet {
self.followerButton.addObserver(self, forKeyPath: "followerButtonTapped", options: .New, context: &kvoContext)
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
print(keyPath)
}
where
private var kvoContext = 0
So, I want: When I click on the button in those Cell it'll run function from the ViewController. But on click on my button does not print anything.
It's my first time with KVOs, am I doing something wrong?

Well, KVO does not work like that. What - addObserver:forKeyPath:options:context: does is register observer to receive KVO notifications when property changed. In your case I suppose "followerButtonTapped" is not a property. Registering for observation
To handle button tapped you need to add target like this (or in IB):
cell.followerButton.addTarget(self, action: #selector(ViewController.onFollowerButtonTap(_:)), forControlEvents: .TouchUpInside)
And add method to your view controller:
func onFollowerButtonTap(sender: UIButton) {
}
To get the model object for this button you can use this extension:
extension UITableView {
func indexPathForView(view: UIView) -> NSIndexPath? {
let hitPoint = view.convertPoint(CGPoint.zero, toView: self)
return indexPathForRowAtPoint(hitPoint)
}
}

Related

Property Observer for color change in swift

I've successfully created a simple iOS app that will change the background color When I click on the button. But. now the problem is I've no idea on how to implement property observer to print out message to the effect of the color whenever the color has changed.
UIColorExtension.swift
import UIKit
extension UIColor {
static var random: UIColor {
// Seed (only once)
srand48(Int(arc4random()))
return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue: CGFloat(drand48()), alpha: 1.0)
}
}
ViewController.Swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// apply random color on view did load
applyRandomColor()
}
#IBAction func btnHandler(_ sender: Any) {
// on each button click, apply random color to view
applyRandomColor()
}
func applyRandomColor() {
view.backgroundColor = UIColor.random
}
}
Please teach me on how to use property observer to monitor and print the color each time the color is changed as I don't have the slightest idea to do so.
At the moment you're just doing this
view.backgroundColor = X
instead, you probably want to make your own "property"....
var mood: UIColor { }
properties can have a "didSet" action...
var mood: UIColor {
didSet {
view.backgroundColor = mood
print("Hello!")
}
}
So now, just use "mood" when you want to change the background color.
mood = X
But, you can ALSO run any other code - where it prints the Hello.
Imagine your program has 100s of places where you do this thing of changing the background color.
If you use a property, you can change what happens "all at once" by just changing the code at print("Hello").
Otherwise, you'd have to change every one of the 100s of places where you do that.
This is a real basic in programming. Enjoy!
If you really need an observer Swift 4 provides a very smart way to do that:
Declare a NSKeyValueObservation property
var observation : NSKeyValueObservation?
In viewDidLoad add the observer, the closure is executed whenever the background color changes.
observation = observe(\.view.backgroundColor, options: [.new]) { _, change in
print(change.newValue!)
}
In Swift there are two types of property observers i.e:
willSet
didSet
You can find it in the Swift documentation here.
if you just need to print color description using property observer then you can create property and set its observers like this:
var backgroundColor = UIColor() {
didSet {
print("didSet value : \(backgroundColor)")
}
}
and your complete code will look like:
import UIKit
class ViewController: UIViewController {
var backgroundColor = UIColor() {
didSet {
print("didSet value : \(backgroundColor)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.applyRandomColor()
}
#IBAction func btnHandler(_ sender: Any) {
// on each button click, apply random color to view
applyRandomColor()
}
func applyRandomColor() {
backgroundColor = UIColor.random
self.view.backgroundColor = backgroundColor
}
}
extension UIColor {
static var random: UIColor {
// Seed (only once)
srand48(Int(arc4random()))
return UIColor(red: CGFloat(drand48()), green: CGFloat(drand48()), blue:
CGFloat(drand48()), alpha: 1.0)
}
}
First add observer like this:
view.addObserver(self, forKeyPath: "backgroundColor", options: [.new], context: nil)
Then override the function:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let color = change?[.newKey] as? UIColor else {return}
print(color)
}
First of all you don't need any observer here because you know when the color is changing as it is changing by you manually by calling the applyRandomColor() function. So you can do here whatever you want to do.
After that if you still want to see how observer works. Then add an observer in class where you want to observe this event.
view.addObserver(self, forKeyPath: "backgroundColor", options: NSKeyValueObservingOptions.new, context: nil)
And here is the event:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "backgroundColor" {
/// Background color has changed
/// Your view which backgroundColor it is
guard let view = object as? UIView else {return}
print(view.backgroundColor)
OR
guard let color = change?[.newKey] as? UIColor else {return}
print(color)
}
}

Making code more organised in swift IOS

I am trying to make my code more organised and reusable. I have some functions and notification that allows the scroll view to be moved up when keyboard shows up and scroll down when keyboard hides. It is all functioning. However, I would imaging these function will be used in multiple parts of my project that has scrollView inside UIViewcontroller. so I want to create a more resuable code rather than writing the same codes in multiple view controllers.
Currently, inside one of my view controller, I have
var keyboard = CGRect()
override func viewDidLoad() {
// Check notifications of keyboard activity
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PostVC.keyboardWillShow(_:)), name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PostVC.keyboardWillHide(_:)), name: UIKeyboardWillHideNotification, object: nil)
// Tap to hide keyboard
let hideTap = UITapGestureRecognizer(target: self, action: #selector(PostVC.hideKeyboard))
hideTap.numberOfTapsRequired = 1
self.view.userInteractionEnabled = true
self.view.addGestureRecognizer(hideTap)
}
func hideKeyboard() {
self.view.endEditing(true)
}
func keyboardWillShow(notification: NSNotification) {
keyboard = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue())!
UIView.animateWithDuration(0.4) {
self.scrollView.contentSize.height = self.view.frame.size.height + self.keyboard.height / 2 + UITabBarController().tabBar.frame.size.height
}
}
func keyboardWillHide(notification: NSNotification) {
UIView.animateWithDuration(0.4) {
self.scrollView.contentSize.height = 0
}
}
I am abit new to trying to make code more re-usable. I am not sure if I need to create a new class or just create a extension of UIViewcontroller and put it in there. I have tried creating an extension of UIViewcontroller and do something like
func keyboardWillShow(notification: NSNotification, _scrollView: UIScrollView) { }
and pass an instance of the scrollview (#IBOutlet weak var scrollView: UIScrollView!) into the function. However, I then run into trouble with doing #selector(keyboardWillShow(_:, keyboard: keyboard, scrollView: scrollView). It gives me an error saying expected expression in list of expressions (I think it is complaining about _:). I might be on a totally wrong path of doing this. Can anyone please help.
Thanks,
I would recommend creating a protocol and adding the default implementation as a protocol extension. Into the protocol you can add that classes that implement it should have a scrollView and a keyboard. Have in mind that protocol extensions are more flexible than base classes and that's why are often preferred in Swift.
Here is an example
protocol Scrollable : class {
var scrollView: UIScrollView { get }
var keyboardRect: CGRect { get set }
}
extension Scrollable where Self : UIViewController {
func registerForKeyboardNotifications() {
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillShowNotification, object: nil, queue: nil, usingBlock: { (notification) in
self.keyboardWillShow(notification)
})
NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillHideNotification, object: nil, queue: nil, usingBlock: { (notification) in
self.keyboardWillHide(notification)
})
}
func deregisterForKeyboardNotification() {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
func keyboardWillShow(notification: NSNotification) {
self.keyboardRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey]!.CGRectValue())!
UIView.animateWithDuration(0.4) {
self.scrollView.contentSize.height = self.view.frame.size.height + self.keyboardRect.height / 2 + UITabBarController().tabBar.frame.size.height
}
}
func keyboardWillHide(notification: NSNotification) {
UIView.animateWithDuration(0.4) {
self.scrollView.contentSize.height = 0
}
}
}
Have in mind that I've used addObserverForName instead of addObserver because the later cannot be used with protocol extensions easily. More about that you can read here - Swift make protocol extension a Notification observer

UITableViewCell Dynamic height with expandable UIView

As I want to create tableview cell that has dynamic height I don't know what height it will take after rendering the content.
And there is a UIView which is hidden. It will render when someone taps on the cell, the UIView is visible.
Now the problem arises when content is larger its starts hiding, and to make that view visible I need the height in Float.
// I am using this as a logic for cell expander.
class var expanderHeight : CGFloat { get { return 160 } }
class var defaultHeight :CGFloat { get { return 140 } }
func checkHeight(){
imageexpander.hidden = (frame.size.height < EaterysTableViewCell.expanderHeight)
}
func watchFrameChanges(){
if !isObserving {
addObserver(self, forKeyPath: "frame", options: [NSKeyValueObservingOptions.New, NSKeyValueObservingOptions.Initial], context: nil)
}
}
func ingnoreFrameChanges(){
if isObserving {
removeObserver(self, forKeyPath: "frame")
isObserving = false
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "frame" {
checkHeight()
}
}
I have three labels like this:
`|label one|
|label two|
|label three|`
How do I add constraints so that content of label one and label two do not get truncated when they have multiple lines (dynamically)?

iOS UITabBar selectionIndicatorImage Y position

I have this spec:
But, i have this:
I have this code:
self.mTabBar.selectionIndicatorImage = UIImage(named: "footer-blue-line")
How can I set the Y position for the selectionIndicatorImage?
Why aren't you using a single "selected" image that has a blue line below it ?
http://s18.postimg.org/c5bjg501h/line.png
Use the image given on the link on the place of "footer-blue-line".
Here you go. You just need to set class of Tab Bar to this class in the interface builder
class MyCustomTabBar: UITabBar
{
var didInit = false
override func layoutSubviews()
{
super.layoutSubviews()
if didInit == false
{
didInit = true
for subview in subviews {
// can't hookup to subviews, so do layer.sublayers
subview.layer.addObserver(self, forKeyPath: "sublayers", options: .New, context: nil)
}
}
}
deinit
{
for subview in subviews
{
subview.layer.removeObserver(self, forKeyPath: "sublayers")
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>)
{
// layer.delegate is usually the parent view
if let l = object as? CALayer, tbButton = l.delegate as? UIView where tbButton.window != nil
{
for v in tbButton.subviews
{
if String(v.dynamicType) == "UITabBarSelectionIndicatorView" {
v.setYOrigin(3)
}
}
}
}
}

Next/Done button using Swift with textFieldShouldReturn

I have a MainView that adds a subview (signUpWindow) when a sign up button is pressed.
In my signUpWindow subview (SignUpWindowView.swift), I set up each field with a function, as an example:
func confirmPasswordText()
{
confirmPasswordTextField.frame=CGRectMake(50, 210, 410, 50)
confirmPasswordTextField.placeholder=("Confirm Password")
confirmPasswordTextField.textColor=textFieldFontColor
confirmPasswordTextField.secureTextEntry=true
confirmPasswordTextField.returnKeyType = .Next
confirmPasswordTextField.clearButtonMode = .WhileEditing
confirmPasswordTextField.tag=5
self.addSubview(confirmPasswordTextField)
}
I have the keyboard moving the signUpWindow up and down when it appears and disappears in the MainView.
SignUpWindowView implements the UITextFieldDelegate
My problem is that I am trying to configure the Next/Done button on the keyboard and am not sure which view (MainView or SignUpWindowView) to add the textFieldShouldReturn function. I have tried both, but can't even get a println to fire to test to see if the function is even being executed. Once I get the textFieldShouldReturn to fire, I am confident I can execute the necessary code to get the Next/Done buttons to do what I want, and will post the final solution to include the Next/Done function.
UPDATED to include an abbreviated version of SignUpWindowView.swift
import UIKit
class SignUpWindowView: UIView,UITextFieldDelegate {
let firstNameTextField:UITextField=UITextField()
let lastNameTextField:UITextField=UITextField()
override func drawRect(rect: CGRect){
func firstNameText(){
firstNameTextField.delegate=self
firstNameTextField.frame=CGRectMake(50, 25, 200, 50)
firstNameTextField.placeholder="First Name"
firstNameTextField.returnKeyType = .Next
self.addSubview(firstNameTextField)
}
func lastNameText(){
lastNameTextField.delegate=self
lastNameTextField.frame=CGRectMake(260, 25, 200, 50)
lastNameTextField.placeholder="Last Name"
lastNameTextField.returnKeyType = .Done
self.addSubview(lastNameTextField)
}
func textFieldShouldReturn(textField: UITextField!) -> Bool{
println("next button should work")
if (textField === firstNameTextField)
{
firstNameTextField.resignFirstResponder()
lastNameTextField.becomeFirstResponder()
}
return true
}
firstNameText()
lastNameText()
}
You need to implement UITextFieldDelegate in your class and set that object as the delegate for the UITextField. Then implement the method textFieldShouldReturn: like this:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
if textField == someTextField { // Switch focus to other text field
otherTextField.becomeFirstResponder()
}
return true
}
In your example you are missing this line:
confirmPasswordTextField.delegate = self
If you have implemented the delegate of course.
I was attempting to test my textfields in the SignUpWindowView.swift, which is where all of the textFields are created. But, since I place SignUpWindowView into my MainViewController as a subview, all of my UITextField "handling" needed to be done in the MainView and NOT its subview.
So here is my entire code (at the moment) for my MainViewController, which handles moving my SignUpWindowView up/down when the keyboard is shown/hidden and then moves from one field to the next. When the user is in the last text field (whose keyboard Next button is now set to Done in the subview) the keyboard tucks away and the user can then submit the form with a signup button.
MainViewController:
import UIKit
#objc protocol ViewControllerDelegate
{
func keyboardWillShowWithSize(size:CGSize, andDuration duration:NSTimeInterval)
func keyboardWillHideWithSize(size:CGSize,andDuration duration:NSTimeInterval)
}
class ViewController: UIViewController,UITextFieldDelegate
{
var keyboardDelegate:ViewControllerDelegate?
let signUpWindow=SignUpWindowView()
let signUpWindowPosition:CGPoint=CGPointMake(505, 285)
override func viewDidLoad()
{
super.viewDidLoad()
// Keyboard Notifications
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "keyboardWillHide:", name: UIKeyboardWillHideNotification, object: nil)
// set the textFieldDelegates
signUpWindow.firstNameTextField.delegate=self
signUpWindow.lastNameTextField.delegate=self
signUpWindow.userNameTextField.delegate=self
signUpWindow.passwordTextField.delegate=self
signUpWindow.confirmPasswordTextField.delegate=self
signUpWindow.emailTextField.delegate=self
}
func keyboardWillShow(notification: NSNotification)
{
var info:NSDictionary = notification.userInfo!
let keyboardFrame = info[UIKeyboardFrameEndUserInfoKey] as! NSValue
let keyboardSize = keyboardFrame.CGRectValue().size
var keyboardHeight:CGFloat = keyboardSize.height
let animationDurationValue = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
var animationDuration : NSTimeInterval = animationDurationValue.doubleValue
self.keyboardDelegate?.keyboardWillShowWithSize(keyboardSize, andDuration: animationDuration)
// push up the signUpWindow
UIView.animateWithDuration(animationDuration, delay: 0.25, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.signUpWindow.frame = CGRectMake(self.signUpWindowPosition.x, (self.signUpWindowPosition.y - keyboardHeight+140), self.signUpWindow.bounds.width, self.signUpWindow.bounds.height)
}, completion: nil)
}
func keyboardWillHide(notification: NSNotification)
{
var info:NSDictionary = notification.userInfo!
let keyboardFrame = info[UIKeyboardFrameEndUserInfoKey] as! NSValue
let keyboardSize = keyboardFrame.CGRectValue().size
var keyboardHeight:CGFloat = keyboardSize.height
let animationDurationValue = info[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber
var animationDuration : NSTimeInterval = animationDurationValue.doubleValue
self.keyboardDelegate?.keyboardWillHideWithSize(keyboardSize, andDuration: animationDuration)
// pull signUpWindow back to its original position
UIView.animateWithDuration(animationDuration, delay: 0.25, options: UIViewAnimationOptions.CurveEaseInOut, animations: {
self.signUpWindow.frame = CGRectMake(self.signUpWindowPosition.x, self.signUpWindowPosition.y, self.signUpWindow.bounds.width, self.signUpWindow.bounds.height)
}, completion: nil)
}
func textFieldShouldReturn(textField: UITextField) -> Bool
{
switch textField
{
case signUpWindow.firstNameTextField:
signUpWindow.lastNameTextField.becomeFirstResponder()
break
case signUpWindow.lastNameTextField:
signUpWindow.userNameTextField.becomeFirstResponder()
break
case signUpWindow.userNameTextField:
signUpWindow.passwordTextField.becomeFirstResponder()
break
case signUpWindow.passwordTextField:
signUpWindow.confirmPasswordTextField.becomeFirstResponder()
break
case signUpWindow.confirmPasswordTextField:
signUpWindow.emailTextField.becomeFirstResponder()
break
default:
textField.resignFirstResponder()
}
return true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().removeObserver(self, name: UIKeyboardWillHideNotification, object: nil)
}
#IBAction func signup()
{
signUpWindow.frame=CGRectMake(signUpWindowPosition.x, signUpWindowPosition.y, 485,450)
signUpWindow.backgroundColor=UIColor.clearColor()
self.view.addSubview(signUpWindow)
}
}
Using tags makes it easier. Assign tags in ascending order to all the text fields you are using on your screen.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let textTag = textField.tag+1
if let nextResponder = textField.superview?.viewWithTag(textTag) as UIResponder
{
//textField.resignFirstResponder()
nextResponder.becomeFirstResponder()
}
else {
// stop editing on pressing the done button on the last text field.
self.view.endEditing(true)
}
return true
}
You connect the DidEndOnExit(I wrote this from memory so maybe its not called this exactly but similar) UIControl event using an #IBAction and in that func you use textF.resignFirstResponder() or .becomeFirstResponder()
EDIT
UITextField is subclass of UIControl and to programatically add a new event you use the addTarget() method. Ex:
func a(sender: AnyObject) {}
textField.addTarget(self, action: "a:", forControlEvents: .EditingDidEndOnExit)
UIControl docs

Resources