I want to manage memory so I want to deinit the UIview when leaving ViewController.
And I try to use keyword "weak" and I get crash because my chatkeyboard is nil.
I dont know why making it crash.
Thanks.
class ChatKeyboard: UIView {
var buttonMic:UIButton = { ()->UIButton in
let ui:UIButton = GeneratorButton()
return ui
}()
override init(frame: CGRect) {
super.init(frame: frame)
print("===) ChatKeyboard init.")
translatesAutoresizingMaskIntoConstraints = false
loadContent()
loadConstrain()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
deinit {
print("===) ChatKeyboard deinit.")
}
func loadContent() {
backgroundColor = UIColor.white
addSubview(buttonMic)
}
func loadConstrain() {
buttonMic.snp.makeConstraints { (make) -> Void in
make.left.equalTo(micLeftPadding)
make.top.equalTo(micTopPadding)
make.width.equalTo(UIScreen.main.bounds.width*0.0581)
make.height.equalTo(UIScreen.main.bounds.height*0.045)
}
}
}
class ChatroomViewController: UIViewController{
weak var chatKeyboard:ChatKeyboard?
override func viewDidLoad() {
super.viewDidLoad()
chatKeyboard = ChatKeyboard(frame: CGRect(x: 0, y: 0, width: 300, height: 44))
}
}
I set breakpoint at "chatKeyboard = ChatKeyboard(frame: CGRect(x: 0, y: 0, width: 300, height: 44))"and then my log print:
===) ChatKeyboard init.
===) ChatKeyboard deinit.
A weak variable will be destroyed as soon as there are no strong references to it.
If you're creating a view and assigning it directly to a weak variable, it will be destroyed immediately. Weak IBOutlets work because they are added to a superview (creating a strong reference) before they are assigned to the variable. You can achieve this by using a local variable before assigning to your property:
let keyboard = ChatKeyboard(...)
view.addSubview(keyboard)
chatKeyboard = keyboard
However, there is no harm in your view controller having strong references to views it cares about, as long as those views don't also have strong references back to the view controller. They'll get destroyed when the view controller is destroyed.
Reason is you have declared your chatKeyBoard as
weak var chatKeyboard:ChatKeyboard?
Which means your viewController will not hold any strong reference to the view you load. Hence reference count of the view will not vary.
In your viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
chatKeyboard = ChatKeyboard(frame: CGRect(x: 0, y: 0, width: 300, height: 44))
}
You instantiate the view but because view was weakly held as soon as the control goes out of the scope of viewDidLoad ARC released the view.
If you want your view to be accessible even after viewDidLoad called declare a strong a refrence
var chatKeyboard:ChatKeyboard?
Hope it helps
In a class or structure, do not use keyword "weak" to describe the property which you will init in the same class or structure.
Related
How to remove subviews?
I am trying to integrate GIF by creating UIView and UIImageView programmatically.
It works fine to show GIF but when the function of hiding if is called, there is no response.
Here are the codes of both functions.
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
lazy var transparentView: UIView = {
let transparentView = UIView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
transparentView.backgroundColor = viewColor.withAlphaComponent(setAlpha)
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.center = transparentView.center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
self.addSubview(self.transparentView)
self.transparentView.addSubview(self.gifImage)
self.transparentView.bringSubview(toFront: self.gifImage)
UIApplication.shared.keyWindow?.addSubview(transparentView)
}
func hideLoaderView() {
self.transparentView.removeFromSuperview()
}
}
A couple of thoughts:
I’d suggest you add a breakpoint or a logging statement in hideLoaderView and make sure you’re getting to that line.
You should make the init method to this class private to make sure you’re not calling hideLoaderView on some separate instance. When dealing with singletons, you want to make sure you can’t accidentally create another instance.
But I tested your code, and it works fine. Your problem probably rests with where and how you call this (and making init private, you might find where you might be using it inappropriately).
In the comments below, you said:
I simply call the function "CustomLoader().hideLoaderView()" Both are being called technically. What do you mean by "where I using it inappropriately?"
That is the root of the problem.
The CustomLoader() of CustomLoader().hideLoaderView() will create a new instance of CustomLoader with its own transparencyView, etc., which is precisely what the problem is. You’re not hiding the old view that was presented earlier, but trying to hide another one that you just created and was never displayed.
If you instead use that static, e.g. CustomLoader.instance.showLoaderView() and CustomLoader.instance.hideLoaderView(), then the problem will go away. Then you will be hiding the same view that your previously showed.
By the way, a few other unrelated observations:
If this is a singleton or shared instance, the convention would be to call that static property shared, not instance.
By the way, you aren’t using this CustomLoader as a UIView, so I’d not make it a UIView subclass. Don’t make it a subclass of anything.
You would obviously eliminate that self.addSubview(transparentView) line, too.
The bringSubview(toFront:) call is unnecessary.
You should avoid referencing UIScreen.main.bounds. You don’t know if your app might be in multitasking mode (maybe this isn’t an issue right now, but it’s the sort of unnecessary assumption that will cause problems at some later date). Just refer to the bounds of the UIWindow to which you’re adding this. You should also update this frame when you show this view, not when you create it (in case you changed orientation in the intervening time, or whatever).
By the way, using keyWindow is discouraged in iOS 13 and later, so you might eventually want to remove that, too.
When adding the gifImage (which I’d suggest renaming to gifImageView because it’s an image view, not an image), you should not reference the center of its superview. That’s the coordinate of the transparent view in its super view’s coordinate system, which could be completely different than the transparent view’s own coordinate system. In this case, it just happens to work, but it suggests a fundamental misunderstanding of view coordinate systems. Reference the bounds of the transparentView, not its center.
If you’re going to expose viewColor and setAlpha, you should pull the setting of the transparentView’s color out of the lazy initializer and into showLoaderView, at the very least. Right now, if you show the loader once, and then change the color, and try to show it again, you won’t see the new color.
The same issue applies with the gif image. So, I’d move that to the didSet observer.
Thus, pulling this all together:
class CustomLoader{
static let shared = CustomLoader()
private init() { }
var dimmingColor: UIColor = .black
var dimmingAlpha: CGFloat = 0.5
var gifName: String = "" { didSet { gifImage.loadGif(name: gifName) } }
lazy var transparentView: UIView = {
let transparentView = UIView()
transparentView.isUserInteractionEnabled = false
return transparentView
}()
lazy var gifImageView: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.contentMode = .scaleAspectFit
gifImage.isUserInteractionEnabled = false
return gifImage
}()
func showLoaderView() {
guard let window = UIApplication.shared.keyWindow else { return }
transparentView.frame = window.bounds
transparentView.backgroundColor = dimmingColor.withAlphaComponent(dimmingAlpha)
gifImageView.center = CGPoint(x: transparentView.bounds.midX, y: transparentView.bounds.midY)
transparentView.addSubview(gifImageView)
window.addSubview(transparentView)
}
func hideLoaderView() {
transparentView.removeFromSuperview()
}
}
Why you are using transparentView while you are have a CustomLoader instance view
Try to use this
class CustomLoader: UIView {
static let instance = CustomLoader()
var viewColor: UIColor = .black
var setAlpha: CGFloat = 0.5
var gifName: String = ""
init() {
super.init(frame: UIScreen.main.bounds)
backgroundColor = viewColor.withAlphaComponent(setAlpha)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var gifImage: UIImageView = {
var gifImage = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 60))
gifImage.backgroundColor = .red
gifImage.contentMode = .scaleAspectFit
gifImage.center = center
gifImage.isUserInteractionEnabled = false
gifImage.loadGif(name: gifName)
return gifImage
}()
func showLoaderView() {
addSubview(self.gifImage)
UIApplication.shared.keyWindow?.addSubview(self)
}
func hideLoaderView() {
removeFromSuperview()
}
}
I have a UIButton that I am placing on a different UIView than the one it is declared in but I want the Target Selector to be in the UIView I declared the button in. The init_button function is getting called because the button is being placed on the view. Here is what I have:
import Foundation
import UIKit
class Menu1 : Element {
var color_base = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
var mock : UIView = UIView()
var button : UIButton = UIButton()
var view_width: CGFloat = 0,
view_height: CGFloat = 0,
menu_vertical_buffer : CGFloat = 0,
menu_width : CGFloat = 0,
button_vertical_buffer : CGFloat = 0,
button_width : CGFloat = 0
required init(mock: UIView) {
super.init(mock: mock)
self.mock = mock
init_dims()
init_button()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func init_dims(){
view_width = mock.frame.width
view_height = mock.frame.height
menu_vertical_buffer = CGFloat(floor(Double(view_height/17.5)))
menu_width = CGFloat(floor(Double(view_width/2.67)))
button_vertical_buffer = CGFloat(floor(Double(menu_vertical_buffer/3.6)))
button_width = menu_width - CGFloat(floor(Double(menu_width/7)))
}
func init_button(){
button = UIButton(frame: CGRect(x: mock.frame.width - menu_width,
y: mock.frame.height - (menu_vertical_buffer + (2 * button_vertical_buffer)),
width: button_width,
height: menu_vertical_buffer))
button.backgroundColor = color_base
button.layer.cornerRadius = button.frame.height/2
button.addTarget(self, action: #selector(changeMenuState), for: .touchUpInside)
mock.addSubview(button)
}
dynamic func changeMenuState(){
print("Menu state changed")
}
}
changeMenuState is the function I am adding as the target selector but the button is in the view mock not in the view that these functions are in.
changeMenuState must be dynamic or #objc exposed like
#objc func changeMenuState(){
print("Menu state changed")
}
My best guess is this might be a scoping issue. You aren't showing where the button is stored or the lifetime of the scope. In a typical scenario, you would define a button in a viewcontroller (or perhaps a cell) like so:
class MyViewController: UIViewController {
var button: UIButton? = nil
override func viewDidLoad() {
super.viewDidLoad()
init_button()
}
}
This way the button stays in scope as long as the view controller is within scope. Your code looks valid so my "best guess" is the references to button are going out of scope.
You probably need to provide more insight on how these views are working together and the scope of their lifetime (if this comment doesn't help you).
Did you set button's frame corretly? You can set mock's clipsToBounds to YES, then observe.
update:
As #Badhan Ganesh comment ,you can see the view stack.
The code you have written is correct and I have given the try on same. The issue I found is that your button is appearing on screen but placed out of bound of mock view, hence button is unable to receive the touch event on it. Try to place button inside mock view bounds by setting the frame correctly.
I was able to solve the problem and it was a scoping issue. I never added the Menu1 class to the mock class or any other view so I believe after it was instantiated and the button placed, that instance was killed so the button was placed but the target was in a class that was no longer in use. what solved my problem was adding mock.addSubview(self) to this class.
I've got a UIControl class and need to do some calculation based on UIImageView location which can be moved with touchesBegan and touchesMoved (everything inside this class).
Than I would like to display it as a UILabel which I've created programmatically.
class control : UIControl{
...
let leftControl: UIImageView = UIImageView(image: UIImage(named: "left-control"))
...
func leftValue() -> String{
var leftValue : String = "0.0"
leftValue = "\(leftControl.center.x)"
return leftValue
}
}
and my ViewController.swift
class ViewController: UIViewController {
let ctrl : Control = Control()
let leftLabel : UILabel = UILabel(frame: CGRect(x: 40, y: 300, width: 150, height: 30))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ctrl.frame.origin = CGPoint(x: 40, y: 400)
leftLabel.text = "\(ctrl.leftValue())" //displays only starting value
view.addSubview(slider)
view.addSubview(leftLabel)
view.addSubview(rightLabel)
}
I know that it's inside the viewDidLoad so it's not updating properly. I was wondering about scheduledTimer but don't know if it's good solution.
You can achieve this using protocols and delegation - in the file for your Control add this :
protocol ControlDelegate: class {
func controlPositionDidChange(leftValue: String)
}
And add a weak var delegate: ControlDelegate? inside Control class.
In the file for view controller make following changes :
class ViewController: UIViewController, ControllDelegate {
let ctrl : Control = Control()
let leftLabel : UILabel = UILabel(frame: CGRect(x: 40, y: 300, width: 150, height: 30))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ctrl.frame.origin = CGPoint(x: 40, y: 400)
ctrl.delegate = self
leftLabel.text = "\(ctrl.leftValue())" //displays only starting value
view.addSubview(slider)
view.addSubview(leftLabel)
view.addSubview(rightLabel)
}
func controlPositionDidChange(leftValue: String) {
leftLabel.text = leftValue
}
}
Now, whenever you want to inform the delegate that your control has changed the position, simply call self.delegate?.controlPositionDidChange(self.leftValue()) in appropriate places.
As usually, there is more in the docs. I highly suggest reading through them as delegation and protocols are widely used in CocoaTouch.
The answer of #Losiowaty describes the solution that most developers choose. But there are (in my opinion) much better ways to achieve it. I prefer the object oriented solution. It might look like more code, but its a much better maintainable way with more reusable code.
A real object oriented solution of your problem might look like that:
// reusable protocol set
protocol OOString: class {
var value: String { get set }
}
// reusable functional objects
final class RuntimeString: OOString {
init(initialValue: String = "") {
self.value = initialValue
}
var value: String
}
final class ViewUpdatingString: OOString {
init(_ decorated: OOString, view: UIView) {
self.decorated = decorated
self.view = view
}
var value: String {
get {
return decorated.value
}
set(newValue) {
decorated.value = newValue
view.setNeedsLayout()
}
}
private let decorated: OOString
private let view: UIView
}
// reusable ui objects
final class MyLabel : UILabel {
init(frame: CGRect, imageXCenter: OOString) {
self.imageXCenter = imageXCenter
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not supported")
}
override func layoutSubviews() {
super.layoutSubviews()
text = imageXCenter.value
}
private let imageXCenter: OOString
}
final class MyControl : UIControl {
init(frame: CGRect, imageXCenter: OOString) {
self.imageXCenter = imageXCenter
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("Not supported")
}
private let imageXCenter: OOString
private let leftControl = UIImageView(image: UIImage(named: "left-control"))
// call this at change
private func updateValue() {
imageXCenter.value = "\(leftControl.center.x)"
}
}
// non reusable business logic
final class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let dependency = RuntimeString(initialValue: "unset")
let leftLabel = MyLabel(
frame: CGRect(x: 40, y: 300, width: 150, height: 30),
imageXCenter: dependency
)
let control = MyControl(
frame: CGRect(x: 40, y: 400, width: 400, height: 400),
imageXCenter: ViewUpdatingString(dependency, view: leftLabel)
)
view.addSubview(leftLabel)
view.addSubview(control)
}
}
The main idea is to extract the dependency of both objects into another object and use a decorator then to automatically update the ui on every set value.
Note:
this approach follows the rules of object oriented coding (clean coding, elegant objects, decorator pattern, ...)
reusable classes are very simple constructed and fullfill exactly one task
classes communicate by protocols with each other
dependencies are given by dependency injection as far as possible
internal object functionality is private (loose coupling)
everything (except the business logic) is designed for reuse -> if you code like that the portfolio of reusable code grows with every day you code
the business logic of your app is more concentrated in one place (in my real coding its even outside the UIViewController)
unittesting of reusable objects is very simple when using fake implementations for the protocols (even mocking is not needed in most cases)
lesser problems with retain cycles, in most cases you do not need weak properties any more
avoiding Null, nil and Optionals (they pollute your code)
...
I have coded a custom UIButton as :
class AccountOpeningButton: UIButton {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
......
}
}
I am able to instantiate this Class successfully using my Storyboard.
Now, i made a UIView & want to add this button in my UIView as :
var customView:UIView = UIView()
customView.frame = CGRect(x: 0, y: 0, width: 350, height: 250)
.....
let fromDateBtn:UIButton = AccountOpeningButton()//Error comes here as : Missing Argument for parameter ‘coder’ in call
customView.addSubview(fromDateBtn)
So please help in in reusing this code dynamically also.
P.S. : I referred http://napora.org/nscoder-and-swift-initialization/
Fatal error: use of unimplemented initializer 'init(coder:)' for class
Class does not implement its superclass's required members
But didn't succeed.
=======================================================================
TRIED
let fromDateBtn:UIButton = UIButton() as! AccountOpeningButton
This throws CastException Could not cast value of type 'UIButton' to '.AccountOpeningButton'
Replace This line
let fromDateBtn:UIButton = AccountOpeningButton()
With This:
let fromDateBtn = AccountOpeningButton()
And add this method in your class
override init(frame: CGRect) {
super.init(frame: frame)
}
You can have more than one init method, but you have to obey the
inheritance and hierarchy rules. And you need to definitely understand
what are called convenience initializers.
For more details find Here
I have a custom UIView that can be simplified down to:
class Node: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let tapGR = UITapGestureRecognizer(target: self, action: "createChildNode")
addGestureRecognizer(tapGR)
userInteractionsEnabled = true
}
/* ... */
func createChildNode() {
let childNode = Node(frame: self.bounds.offset(dx: 100, dy: 100))
self.addSubview(childNode)
}
}
The first (root) node is created in the view controller:
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(Node(x: 100, y: 100, width: 150, height: 40))
}
For the root node, everything works as expected. However, for the children of the root node, nothing works.
To make sure that I wasn't using the UITapGestureRecognizer incorrectly, I tried to override the raw touchesBegan function for the Node class and do a simple println("tapped"). However the problem persisted and seems to be that the subviews do not receive any touches at all.
What could be causing this?
Sounds like you just need to pass the touch events through the view hierarchy. This answer should help:
https://stackoverflow.com/a/21436355/3259329