I have used blocks in cell for getting switch value but now my problem is that deinit not called where i used the blocks. it is completely working previously but in swift 3.0 it is not working.
My switch cell :
import UIKit
class CellSwitch: UITableViewCell {
#IBOutlet weak var objSwitch: UISwitch!
#IBOutlet weak var btnInfo: UIButton!
#IBOutlet weak var lblTitle: UILabel!
var blockSwitch_Change : ((_ isOn:Bool) -> Void)!
var blockBtn_Clicked : (() -> Void)!
override func awakeFromNib() {
super.awakeFromNib()
self.lblTitle.font = Font.init(Font.FontType.custom(Font.FontName.NotoSans_Regular), size: Font.FontSize.standard(Font.StandardSize.Regular)).instance
// Initialization code
}
//MARK:- switch object change
#IBAction func switch_ValChanged(_ obj:UISwitch){
self.blockSwitch_Change?(obj.isOn)
}
//MARK:- button clicked
#IBAction func btnInfo_Clicked(_ sender: UIButton) {
self.blockBtn_Clicked?()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Uses of this cell
let cell = tableView.dequeueReusableCell(withIdentifier: CellSwitch.identifier) as? CellSwitch
cell?.lblTitle.textColor = Color.custom(hexString: objModel.titleLblColor, alpha: 1.0).value
cell?.lblTitle.text = objModel.strTitle
cell?.objSwitch.isOn = objModel.isOn
cell?.btnInfo.isHidden = !objModel.isInfoBtn
cell?.blockBtn_Clicked = {
print("button clicked")
}
cell?.blockSwitch_Change = { (isOn) in
print("switch value changed \(isOn)")
}
if objModel.isEnable == false
{
cell?.isUserInteractionEnabled = false
cell?.contentView.alpha = 0.5
}
else
{
cell?.isUserInteractionEnabled = true
cell?.contentView.alpha = 1.0
}
return cell!
Also if i comment this two blocks then my deinit will called.
It sounds like you are creating a retain cycle by referencing the view controller strongly inside the blocks. Instead you should create a weak reference to the vc to use. This is my preferred method
cell?.blockBtn_Clicked = { [weak self]
print("button clicked")
self?.viewModel.//do something
}
cell?.blockSwitch_Change = { [weak self] (isOn) in
print("switch value changed \(isOn)")
self?.viewModel.//do something
}
The [weak self] portion will pass a weak reference of self into the block, although note this reference is now an optional. You can then use optional chaining or unwrap it after that.
Related
I have a custom view CustomSegmentedControl in my app which have its protocol with changed index function as below,
protocol SegmentControllerDelegate:class {
func indexChanged(index : Int)
}
class CustomSegmentedControl: UIView {
weak var delegate : SegmentControllerDelegate?
#objc func buttonAction(sender:UIButton) {
for (buttonIndex, btn) in buttons.enumerated() {
btn.setTitleColor(textColor, for: .normal)
if btn == sender {
let selectorPosition = frame.width/CGFloat(buttonTitles.count) * CGFloat(buttonIndex)
selectedIndex = buttonIndex
//delegate?.changeToIndex(index: buttonIndex)
if(delegate != nil){
delegate?.indexChanged(index: buttonIndex)
}else{
print(buttonIndex)
}
UIView.animate(withDuration: 0.3) {
self.selectorView.frame.origin.x = selectorPosition
}
btn.setTitleColor(selectorTextColor, for: .normal)
}
}
}
I added a view to my ViewController Mail and set the class to my custom view and make outlet for it and use the protocol as well like below,
import UIKit
class Mail: UIViewController, SegmentControllerDelegate {
func indexChanged(index: Int) {
print(index)
switch index {
case 0:
container.bringSubviewToFront(inbox)
break
case 1:
container.bringSubviewToFront(outbox)
break
default:
break
}
}
#IBOutlet weak var segment: CustomSegmentedControl!{
didSet{
segment.setButtonTitles(buttonTitles: ["First","Second"])
segment.selectorTextColor = .orange
segment.selectorViewColor = .orange
}
}
#IBOutlet weak var container: UIView!
var inbox : UIView!
var outbox : UIView!
override func viewDidLoad() {
super.viewDidLoad()
segment.delegate = self
inbox = Inbox().view
outbox = GPS().view
container.addSubview(inbox)
container.addSubview(outbox)
}
But the protocol is always nil and the function never be called !?, What Im missing here ?
Any help will be much appreciated
My switch is inside of a nib which is inside of a tableview header. When I click the switch it only does the pushSwitch.isOn and not !pushSwitch.isOn. It only gives me the values inside of the pushSwitch and doesn't seem to go inside of !pushSwitch.isOn
Thanks
protocol PhoneNotificationHeaderViewDelegate: class {
func copyPreferredSettingsRequested()
func textNotificationSwitchTapped()
func pushNotificationSwitchTapped()
}
class PhoneNotificationHeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var textSwitch: UISwitch!
#IBOutlet weak var pushSwitch: UISwitch!
#IBOutlet weak var copyPreferredSettingsButton: UIButton!
#IBOutlet weak var notificationView: UIView!
weak var delegate: PhoneNotificationHeaderViewDelegate?
UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
}
#IBAction func textNotificationSwitchTapped(_ sender: AnyObject) {
self.delegate?.textNotificationSwitchTapped()
}
#IBAction func pushNotificationSwitchTapped(_ sender: UISwitch) {
self.delegate?.pushNotificationSwitchTapped()
}
#IBAction func copyPreferredSettingsButtonTapped() {
self.delegate?.copyPreferredSettingsRequested()
}
override var backgroundColor: UIColor? {
didSet {
print("backgroundColor")
}
}
Here is my switch inside my VC:
extension PhoneNotificationViewController: PhoneNotificationHeaderViewDelegate {
func pushNotificationSwitchTapped() {
guard let phoneNotificationHeader = Bundle(for: type(of: self)).loadNibNamed("PhoneNotificationHeaderView", owner: self, options: nil)?.first as? PhoneNotificationHeaderView else {
return
}
if phoneNotificationHeader.pushSwitch.isOn{
//Disable Firebase from sending
Globals.sharedInstance.pushStatus = true
phoneNotificationHeader.pushSwitch.setOn(false, animated: true)
}else{
Globals.sharedInstance.pushStatus = false
phoneNotificationHeader.pushSwitch.setOn(true, animated: true)
}
self.refreshUI()
}
There is no such thing as a "a nib which is inside of a tableview header". Every time you load a nib by saying loadNibNamed, you get a fresh copy of the view that it contains. So every time you call pushNotificationSwitchTapped you are a getting a whole new switch fresh from the nib, and sticking it in your interface. That is probably not what you intend! And of course the switch comes from the nib in only one state, namely the state that you set it in the nib editor: it is On.
You need to abandon completely this incorrect architecture based on a misapprehension about what a nib is. You should load the nib once, and from then on you should refer to the switch by talking to the switch itself, the one that is now in your interface.
I want to update the layout of a popover, I've already tried using the setNeedsLayout() and layoutIfNeeded() methods but couldn't find a solution.
I have a simple screen with 4 buttons that call a popover, within the popover there are 2 extra buttons:
Button -> hides button3, and moves button1 and button2 to the left.
reposition -> changes the sourceview of the popover from the button1/2/3/4 (sender) to button4.
Picture of the layout
The problem I have is that I'm not able to update/refresh the screen, and the popover keeps pointing to the former source view position.
Picture of the popover
Somehow I need a method that updates my view and which is stronger than layoutIfNeeded() because if the screen is changed from portrait/landscape the popover changes automatically. Here I changed portrait/landscape/portrait
GitHub of the project. GitHub
The code used is the following:
class ViewController: UIViewController, ButtonDidDisappearDelegate {
#IBOutlet weak var button1: UIButton!
#IBOutlet weak var button2: UIButton!
#IBOutlet weak var button3: UIButton!
#IBOutlet weak var button4: UIButton!
#IBOutlet weak var constrain2: NSLayoutConstraint!
var popVC: PopVC?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonPopover(_ sender: UIButton) {
configureAndPresentPopover(from: sender)
}
func buttonDisappear() {
self.constrain2.constant = 100
self.button3.isHidden = !self.button3.isHidden
if !self.button3.isHidden {
self.constrain2.constant = 10
}
}
func repositionPopover() {
DispatchQueue.main.async {
if let popVC = self.popVC {
self.self.popoverPresentationController(popVC.popoverPresentationController!, willRepositionPopoverTo: self.button4.bounds, in: self.button4)
}
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
}
extension ViewController: UIPopoverPresentationControllerDelegate {
func configureAndPresentPopover(from sender: UIButton) {
popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") as? PopVC
guard let popVC = popVC else {
return
}
popVC.popOverDelegate = self
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = sender
popOverVC?.sourceRect = sender.bounds
popVC.preferredContentSize = CGSize(width: 300, height: 60)
popOverVC?.permittedArrowDirections = .down
self.present(popVC, animated: true)
}
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController,
willRepositionPopoverTo rect: CGRect,
in view: UIView) {
popoverPresentationController.sourceView = view
popoverPresentationController.sourceRect = rect
}
}
and the popover controller:
protocol ButtonDidDisappearDelegate: class {
func configureAndPresentPopover(from sender: UIButton)
func buttonDisappear()
func repositionPopover()
}
class PopVC: UIViewController {
weak var popOverDelegate: ButtonDidDisappearDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBOutlet weak var popoverButton: UIButton!
#IBAction func popoverAction(_ sender: Any) {
popOverDelegate?.buttonDisappear()
DispatchQueue.main.async {
self.view.setNeedsLayout()
self.view.layoutIfNeeded()
}
}
#IBAction func reposition(_ sender: Any) {
popOverDelegate?.repositionPopover()
}
}
You need to specify the new position of the arrow and ask for the layout of the presentation controller's view, not only the presented view.
Try :
func repositionPopover() {
let popOverController = presentedViewController?.presentationController as? UIPopoverPresentationController
popOverController?.sourceView = button4
popOverController?.containerView?.setNeedsLayout()
popOverController?.containerView?.layoutIfNeeded()
}
You can try setting the sourceview and sourcerect once again. call this function when the sourcview bounds are changed.
I have two view controllers, one is the timeline the second one is for the creation. In that second view controller I have a sub view. This sub view is an SKView. Now every time I segue to it, it increases the memory usage by 2mb (on a real device), but the memory usage stays the same when I unwind it.
So it is like this: I start with a usage of 12mb, then it gets 14-15mb. After the unwind it stays around 14-15mb. After the second segue to it, it becomes 17mb... and so on.
This is the code used in the timeline controller:
#IBAction func createButtonAct(sender: AnyObject) {
self.performSegueWithIdentifier("create", sender: self)
}
#IBAction func unwindFromCreation(segue: UIStoryboardSegue) {
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "create"{
let vc = segue.destinationViewController as! CreateViewController
if vc.currCountry == nil || vc.currCountry != self.currCountry{
vc.currCountry = self.currCountry
}
}
}
And this is the code in the create View Controller:
class CreateViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var bubbleView: SKView!
#IBOutlet var arrow: UIButton!
var ref: Firebase!
let categories = CatsAndColors.categories
#IBOutlet var doneButton: UIButton!
#IBOutlet var titleField: UITextField!
#IBOutlet var descriptionView: KMPlaceholderTextView!
var choosedCat: String!
var selectedCats: NSMutableArray!
var currCountry:String!
var tap: UITapGestureRecognizer!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
UIApplication.sharedApplication().statusBarStyle = .Default
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.arrow.transform = CGAffineTransformMakeRotation(3.14159)
})
titleField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
titleField.addTarget(self, action: "textFieldDidBegin:", forControlEvents: UIControlEvents.EditingDidBegin)
titleField.addTarget(self, action: "textFieldDidEnd:", forControlEvents: UIControlEvents.EditingDidEnd)
// the targets get removed in viewWillDisappear
selectedCats = NSMutableArray()
}
override func viewDidLoad() {
super.viewDidLoad()
ref = Firebase(url: "https://blabber2.firebaseio.com")
self.doneButton.enabled = false
doneButton.setBackgroundImage(UIImage(named: "Done button inactive"), forState: .Disabled)
doneButton.setTitleColor(UIColor(netHex: 0xF6F6F6), forState: .Disabled)
doneButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
self.setupBubbles()
self.descriptionView.delegate = self
}
func setupBubbles(){
let floatingCollectionScene = ChooseBubblesScene(size: bubbleView.bounds.size)
// floatingCollectionScene.scaleMode = .AspectFit
/*let statusBarHeight = CGRectGetHeight(UIApplication.sharedApplication().statusBarFrame)
let titleLabelHeight = CGRectGetHeight(self.tibleLabel.frame)*/
bubbleView.presentScene(floatingCollectionScene)
for (category, color) in categories {
let node = ChooseBubbleNode.instantiate()
node!.vc = self
node!.fillColor = SKColor(netHex: color)
node!.strokeColor = SKColor(netHex: color)
node!.labelNode.text = category
floatingCollectionScene.addChild(node!)
}
}
...
And the catsAndColors struct looks like this:
struct CatsAndColors{
static var categories = ["Crime":0x5F5068, "Travel":0xFBCB43, "Religion":0xE55555, "Tech":0xAF3151, "Economy":0x955BA5, "Games":0xE76851, "Climate":0x6ED79A, "Books":0xE54242, "History":0x287572, "Clothes":0x515151, "Sports":0x4AB3A7, "Food":0xD87171, "Politics":0x5FA6D6, "Music":0xDD2E63, "Tv-shows":0x77A7FB]
}
Maybe you have created some sort of retain cycle between your view controllers.
If both view controllers hold a reference to each other, then try declaring one of the references as weak.
For more information on the topic read Resolving Strong Reference Cycles Between Class Instances.
I solved the problem, it was strong reference in the sknode file.
Thank you for your answers.
Just learning so I am sure there are much better ways to code what I have so far. Anyways, I have a simple task list that is saved to core data. You can segue to another VC where you can edit or create new task. I wanted to be able to put a uiswitch in the custom cell that would allow you to set the task as complete and then reload the tableview. I have searched all over and found this Select UITableView's row when tapping on its UISwitch in Swift but can't seem to translate a solution. Any help would be great.
enter image description here
I am not sure if I am supposed to code the save within the custom cell or just or pass delegate.
Here is my custom cell
protocol SettingCellDelegate : class {
func didChangeSwitchState(sender: TaskCell, isOn: Bool)
}
class TaskCell: UITableViewCell {
var delegate: SettingCellDelegate!
#IBOutlet weak var taskImage: UIImageView!
#IBOutlet weak var arrowImage: UIImageView!
#IBOutlet weak var dueDateLabel: UILabel!
#IBOutlet weak var dueDateValue: UILabel!
#IBOutlet weak var dueTimeLabel: UILabel!
#IBOutlet weak var dueTimeValue: UILabel!
#IBOutlet var taskCellStatus: UITextField!
#IBOutlet var taskCellSwitchDisplay: UISwitch!
#IBAction func taskCellSwitchAction(sender: AnyObject) {
if (taskCellSwitchDisplay.on) {
taskCellStatus.text = "Completed"
} else {
taskCellStatus.text = "Active"
}
}
#IBOutlet weak var taskNameValue: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
taskCellSwitchDisplay.addTarget(self, action: "switchChanged:", forControlEvents: UIControlEvents.ValueChanged)
}
func configureCell(task: Tasks) {
taskImage.image = UIImage(named: "Icon.png")
arrowImage.image = UIImage(named: "arrow_filled.png")
dueDateLabel.text = "Due Date:"
dueDateValue.text = task.taskDate
dueTimeLabel.text = "Due Time:"
dueTimeValue.text = task.taskTime
taskNameValue.text = task.taskName
taskCellStatus.text = task.status
//taskCellStatus.hidden = true
if (taskCellStatus.text == "Completed") {
taskCellSwitchDisplay.on = true
} else {
taskCellSwitchDisplay.on = false
}
}
func switchChanged(taskCellSwitchDisplay: UISwitch) {
_ = taskCellSwitchDisplay.on
if taskCellSwitchDisplay.on == true {
let switchValue = "Completed"
print("Switch Variable: \(switchValue)")
delegate.didChangeSwitchState(self, isOn: true)
} else {
let switchValue = "Active"
print("Switch Variable: \(switchValue)")
delegate.didChangeSwitchState(self, isOn: false)
}
}
Here is where I am getting the delegate in my tableview
extension TaskCell: SettingCellDelegate {
func didChangeSwitchState(sender: TaskCell, isOn: Bool) {
if isOn == true {
print("true")
} else {
print("false")
}
}