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)
}
}
}
}
}
Related
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)
}
}
I'm trying to get the height of all of a table's contents so I can update the container it is in. This will allow the container and other views in the scroll view to scroll together.
Swift:
var tableViewHeight: CGFloat {
tableView.layoutIfNeeded()
return tableView.contentSize.height
}
Objective-C
- (CGFloat)tableViewHeight {
[tableView layoutIfNeeded];
return [tableView contentSize].height;
}
Swift 3
override func viewDidLoad() {
super.viewDidLoad()
myTbleView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
}
override func viewWillDisappear(_ animated: Bool) {
myTbleView.removeObserver(self, forKeyPath: "contentSize")
super.viewWillDisappear(true)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if(keyPath == "contentSize"){
if let newvalue = change?[.newKey]
{
let newsize = newvalue as! CGSize
tableViewHeightConstraint.constant = newsize.height
}
}
}
Hope this will help you.
var obs: NSKeyValueObservation?
obs = tableView.observe(\.contentSize, options: .new) { (_, change) in
guard let height = change.newValue?.height else { return }
self.constantHeight.constant = height
}
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)
}
}
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)?
I'm using popoverPresentationController to show my popover. The UITableViewController used to show as popover is created programmatically and will usually contain 1 to 5 rows. How do I set up this popover to adjust the size to the content of the tableview?
Code for my popover:
if recognizer.state == .Began {
let translation = recognizer.locationInView(view)
// Create popoverViewController
var popoverViewController = UITableViewController()
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
popoverViewController.tableView.backgroundColor = UIColor.popupColor()
// Settings for the popover
let popover = popoverViewController.popoverPresentationController!
popover.delegate = self
popover.sourceView = self.view
popover.sourceRect = CGRect(x: translation.x, y: translation.y, width: 0, height: 0)
popover.backgroundColor = UIColor.popupColor()
presentViewController(popoverViewController, animated: true, completion: nil)
}
In your UITableViewController's viewDidLoad() you can add an observer:
self.tableView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
Then add this method:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
self.preferredContentSize = tableView.contentSize
}
Lastly, in viewDidDisappear(), make sure you remove the observer:
tableView.removeObserver(self, forKeyPath: "contentSize")
This way the popover will automatically adjust size to fit the content, whenever it is loaded, or changed.
Checkout the preferredContentSize property of UIViewController:
let height = yourDataArray.count * Int(popOverViewController.tableView.rowHeight)
popOverViewController.preferredContentSize = CGSize(width: 300, height: height)
Override the preferredContentSize property in your extension of the uitableviewcontroller as following:
override var preferredContentSize: CGSize {
get {
let height = calculate the height here....
return CGSize(width: super.preferredContentSize.width, height: height)
}
set { super.preferredContentSize = newValue }
}
For calculating the height check out tableView.rectForSection(<#section: Int#>)
Simple dynamic answer for Swift 4.x and Swift 5.x involving no size-computation (modern version of Bo Frese answer):
private var contentSizeObserver : NSKeyValueObservation?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
contentSizeObserver = tableView.observe(\.contentSize) { [weak self] tableView, _ in
self?.preferredContentSize = CGSize(width: 320, height: tableView.contentSize.height) // Here I fixed the width but you can do whatever you want
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
contentSizeObserver?.invalidate()
contentSizeObserver = nil
}
For swift 4 if you want to observe the content size I found this to be the optimal solution. Reporting it here since I did not find a complete example online:
class MyTableViewController: UITableViewController {
private var kvoContext = 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
addObserver(self, forKeyPath: #keyPath(tableView.contentSize), options: .new, context: &kvoContext)
}
override func viewDidDisappear(_ animated: Bool) {
removeObserver(self, forKeyPath: #keyPath(tableView.contentSize))
super.viewDidDisappear(animated)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &kvoContext, keyPath == #keyPath(tableView.contentSize),
let contentSize = change?[NSKeyValueChangeKey.newKey] as? CGSize {
self.popoverPresentationController?.presentedViewController.preferredContentSize = contentSize
}
}
}
First thing first: All comments are good and help full.
I have did little change in my logic which makes my VC as reusable component.
Calling this method inside viewWillAppear:(BOOL)animated:
-(void) setPopOverPreferedContentHeight {
if (self.popoverPresentationController && self.tableView.contentSize.height < MAX_POPOVER_HEIGHT) {
self.preferredContentSize=self.tableView.contentSize;
} else if (self.popoverPresentationController){
self.preferredContentSize = CGSizeMake(self.tableView.contentSize.width, MAX_POPOVER_HEIGHT);
}
}