ChildViewController and IBOutlet UIView in SWIFT - ios

I spent days to try to solve this problem but can't find any solution (except programmaticaly):
I have 2 UIViewController where the 2nd is a UIChildViewController.
In the ChildViewController I have an IBOutlet UIView class's attribute linked to a CustomClassUIview in the storyboard.
This CustomClassUIview have methods and attribute to update the shape layer define inside this class.
The problem is when I try to access to one attribute of this custom uiview, it returns nil.
I know that the IBOutlet is fired outside viewDidLoad, viewWillAppear,... but I don't know how to keep alloc.
I did with storyboard to be easier to design but if I did this only programmaticaly it works.
Any help please.
class ChildViewController: UIViewController {
#IBOutlet weak var customUIView: CustomClassUIView!
var upperValueProgress:CGFloat = 0 {
didSet {
self.customUIView.progress = upperValueProgress
updateLayerFrames()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.greenColor()
}
override func viewWillAppear(animated: Bool) {
customUIView.progress = 0
}
func updateLayerFrames() {
customUIView.reveal()
}
}
class FirstViewController: UIViewController {
var customUIViewController:ChildViewController!
override func viewDidLoad() {
super.viewDidLoad()
self.customUIViewController = ChildViewController()
}
...
func update(){
self.customUIViewController.upperValueProgress = 56 // Example
}
,;
#IBDesignable class CustomClassUIview: UIView {
let pathLayer = CAShapeLayer()
var circleRadius: CGFloat {
get {
return self.frame.width / 2
}
set {
}
}
#IBInspectable var progress: CGFloat = 0 {
didSet {
reveal()
}
}
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
reveal()
}
func configure() {
pathLayer.frame = self.bounds
pathLayer.lineWidth = 2
pathLayer.backgroundColor = UIColor.clearColor().CGColor
pathLayer.fillColor = UIColor.clearColor().CGColor
pathLayer.strokeColor = UIColor.redColor().CGColor
layer.addSublayer(segmentTimerPathLayer)
backgroundColor = UIColor.whiteColor()
progress = 0
}
func circleFrame() -> CGRect {
var circleFrame = CGRect(x: self.bounds.minX, y: self.bounds.minY, width: 2*circleRadius, height: 2*circleRadius)
circleFrame.origin.x = 0
circleFrame.origin.y = 0
return circleFrame
}
func circlePath() -> UIBezierPath {
return UIBezierPath(ovalInRect: circleFrame())
}
override func layoutSubviews() {
super.layoutSubviews()
pathLayer.frame = bounds
pathLayer.path = circlePath().CGPath
}
func reveal() {
println(progress)
}
}

Related

Round Corners in custom UICollectionViewCell not working in Swift

I have custom UICollectionViewCell and try to round corners for button, which doesn't work.
I had the same problem for ViewController and the problem was that I was doing rounding in viewDidLoad instead of subviewsDidLoad.
I had no idea what is a problem now.
sharing my code.
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
initialsButton.layer.cornerRadius = 0.5 * initialsButton.frame.size.width
initialsButton.clipsToBounds = true
initialsButton.layer.masksToBounds = true
}
-> but I also tried without .clipsToBounds and .masksToBounds. Same result.
Here is the result. It is not a circle, it makes corner wrongly
SEE THIS RESULT
The problem was that I was doing rounding corners in awakeFromNib(), instead you MUST do it in layoutSubviews() and it works nicely.
I suppose you are using IBOutlet's
Create a class something like this, add button to your cell in storyboard, edit there how ever you want, it easier. Your code up is not good, just remove size, frame.height / 2 is what you want if you are going that way.
#IBDesignable
class RoundedBtn: UIButton {
#IBInspectable var cornerRadius: Double {
get {
return Double(self.layer.cornerRadius)
}set {
self.layer.cornerRadius = CGFloat(newValue)
}
}
#IBInspectable var borderWidth: Double {
get {
return Double(self.layer.borderWidth)
}
set {
self.layer.borderWidth = CGFloat(newValue)
}
}
#IBInspectable var borderColor: UIColor? {
get {
return UIColor(cgColor: self.layer.borderColor!)
}
set {
self.layer.borderColor = newValue?.cgColor
}
}
#IBInspectable var shadowColor: UIColor? {
get {
return UIColor(cgColor: self.layer.shadowColor!)
}
set {
self.layer.shadowColor = newValue?.cgColor
}
}
#IBInspectable var shadowOffSet: CGSize {
get {
return self.layer.shadowOffset
}
set {
self.layer.shadowOffset = newValue
}
}
#IBInspectable var shadowRadius: Double {
get {
return Double(self.layer.shadowRadius)
}set {
self.layer.shadowRadius = CGFloat(newValue)
}
}
#IBInspectable var shadowOpacity: Float {
get {
return self.layer.shadowOpacity
}
set {
self.layer.shadowOpacity = newValue
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
self.titleLabel?.numberOfLines = 0
self.titleLabel?.textAlignment = .center
self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .vertical)
self.setContentHuggingPriority(UILayoutPriority.defaultLow + 1, for: .horizontal)
}
override var intrinsicContentSize: CGSize {
let size = self.titleLabel!.intrinsicContentSize
return CGSize(width: size.width + contentEdgeInsets.left + contentEdgeInsets.right, height: size.height + contentEdgeInsets.top + contentEdgeInsets.bottom)
}
override func layoutSubviews() {
super.layoutSubviews()
titleLabel?.preferredMaxLayoutWidth = self.titleLabel!.frame.size.width
}
}

How hide blur for UIPopoverPresentationController in swift?

I've implemented popover for UIButton:
#objc func showList(sender: AnyObject?) {
guard let buttonView = sender?.value(forKey: "view") as? UIButton else { return }
guard let popVC = R.storyboard.first.VC() else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = buttonView
popOverVC?.sourceRect = CGRect(x: 0, y: 190, width: 0, height: 0)
popOverVC?.permittedArrowDirections = .init(rawValue: 0)
popVC.preferredContentSize = CGSize(width: 150, height: 250)
showedViewController.present(popVC, animated: true, completion: nil)
}
extension VCInteractor: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
It's working good, but I need one fix - I want to hide UIVisualEffectBackdropView for my popover (it's like blur under my UITableViewController). So, I can't find any info how to disable (hide) this blur, do you have any ideas?
I've fixed my issue with my custom class
class PopoverBackgroundView: UIPopoverBackgroundView {
override init(frame: CGRect) {
super.init(frame: frame)
self.layer.shadowColor = UIColor.clear.cgColor
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override static func contentViewInsets() -> UIEdgeInsets {
return UIEdgeInsets.zero
}
override static func arrowHeight() -> CGFloat {
return 0
}
override var arrowDirection: UIPopoverArrowDirection {
get { return UIPopoverArrowDirection.down }
set {
setNeedsLayout()
}
}
override var arrowOffset: CGFloat {
get { return 0 }
set {
setNeedsLayout()
}
}
}
and I've added it like that
popOverVC?.popoverBackgroundViewClass = PopoverBackgroundView.self
that's all
here i have a very simple solution.
let appearance = UIVisualEffectView.appearance(whenContainedInInstancesOf: [NSClassFromString("_UIPopoverStandardChromeView")! as! UIAppearanceContainer.Type])
appearance.effect = UIVisualEffect()
add the code to the AppDelegate.swift file or anywhere the app is getting initialized.
I suggest adding this to the PopoverBackgroundView that Vadim Nikolaev has posted above. This removes the "clear halo" effect when removing the blur.
`override func didMoveToWindow() {
super.didMoveToWindow()
if #available(iOS 13, *) {
// iOS 13 (or newer)
if let window = UIApplication.shared.keyWindow {
let transitionViews = window.subviews.filter { String(describing: type(of: $0)) == "UITransitionView" }
for transitionView in transitionViews {
let shadowView = transitionView.subviews.filter { String(describing: type(of: $0)) == "_UICutoutShadowView" }.first
shadowView?.isHidden = true
}
}
}
}`

UIButton backgroundRect(forBounds:) not being called

I have a custom hexagon shaped button and I want to disable the background color so it wont create a square shape around the hexagon itself.
I am using the backgroundRect(forBounds:) method to override the background's rect.
Here is the full implementation:
class HexaButton : UIButton {
var shape : CAShapeLayer!
func setup() {
if shape == nil {
shape = CAShapeLayer()
shape.fillColor = self.backgroundColor?.cgColor
shape.strokeColor = UIColor.cyan.cgColor
shape.lineWidth = 5.0
self.backgroundColor = UIColor.yellow
self.layer.addSublayer(shape)
}
let side : CGFloat = 6
let r = frame.height/side
shape.path = ShapeDrawer.polygonPath(x: frame.width/2, y: frame.height/2, radius: r, sides: Int(side), pointyness: side/2.0)
}
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
return .zero
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override func layoutSubviews() {
super.layoutSubviews()
setup()
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if shape?.path?.contains(point) == true {
return self
} else {
return nil
}
}
}
For some unknown reason, the backgroundRect(forBounds:) isn't being called.
Any Ideas?
You need to set the backgroundImage property of your UIButton and backgroundRect method will be called.

Setting the frame of a sublayer inside of layoutSubviews() makes changing it impossible. Any workaround for that?

I'm working on a customizable UITextField (see code below). I have added a border at the bottom (you can set it in the storyboard). However, I had problems setting the frame of the CALayer that this border consists of.
If I set it inside the didSet method of var showBottomBorder it doesn't appear on the screen. I think this is because the frame (of the UITextField) hasn't been calculated yet (maybe didSet gets called before that).
So I moved it to the layoutSubviews() method (see code below). This works perfectly.
But now I have another problem. I can't really change that frame anymore. Every time I change it, it gets reset by layoutSubviews() which I think is called then.
At the bottom of my code, there is the method textFieldDidBeginEditing. In there, I wanted to move up my bottom border (animated). But it doesn't work. The border does not move anywhere. And like I said, I think it's because I set the frame inside the layoutSubviews() method.
Is there a better way to set the frame of the bottom border? A way which allows me to change stuff?
#IBDesignable
class CustomizableTextField: UITextField, UITextFieldDelegate {
// MARK: - Properties
private var bottomBorder = CALayer()
// MARK: - #IBInspectables
#IBInspectable var roundCorners: CGFloat = 0 {
didSet {
self.layer.cornerRadius = roundCorners
self.clipsToBounds = true
}
}
/** -- */
#IBInspectable var borderWidth: CGFloat = 1.0 {
didSet {
self.layer.borderWidth = self.borderWidth
}
}
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
self.layer.borderColor = self.borderColor.cgColor
}
}
/** -- */
/** -- */
private var showBottomBorder: Bool = false {
didSet {
switch showBottomBorder {
case true:
bottomBorder.borderColor = self.bottomBorderColor.cgColor
bottomBorder.borderWidth = self.bottomBorderWidth
self.layer.addSublayer(bottomBorder)
self.layer.masksToBounds = true
break
case false:
bottomBorder.removeFromSuperlayer()
break
}
}
}
#IBInspectable var bottomBorderWidth: CGFloat = 1.0 {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
#IBInspectable var bottomBorderColor: UIColor = UIColor.white {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
/** -- */
/** -- */
// Somwhow, the default panel for my font color doesn't change anything, so I created this
#IBInspectable var fixedFontColor: UIColor = UIColor.white {
didSet {
self.textColor = fixedFontColor
}
}
#IBInspectable var placeholderFontColor: UIColor = UIColor.white {
didSet {
var placeholderTxt = ""
if let txt = self.placeholder {
placeholderTxt = txt
}
self.attributedPlaceholder = NSAttributedString(string: placeholderTxt, attributes: [NSForegroundColorAttributeName: placeholderFontColor])
}
}
/** -- */
// MARK: - Overrides and Initializers
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
// HERE
bottomBorder.frame = CGRect(x: 0, y: self.frame.size.height - self.bottomBorderWidth, width: self.frame.size.width, height: self.frame.size.height)
}
// setting the textField delegate to self
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//self.borderStyle = .none
self.delegate = self
}
// MARK: - Events
func textFieldDidBeginEditing(_ textField: UITextField) {
}
You can use and extension on UITextFiled for setting the border.
And keep a reference to it with KVC.
By overriding LayoutSubview, every the layout will change, we'l check if the border exists, if so remove it, and re-create a new one with the new frame:
import UIKit
let MyTopBorder = "myTopBorder"
let MyBottomBorder = "myBottomBorder"
struct Defaults {
static let width = CGFloat(1.0)
static func bottonBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: view.frame.size.height - Defaults.width, width: view.frame.size.width, height: view.frame.size.height)
}
static func topBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: CGFloat(0) , width: view.frame.size.width, height: Defaults.width)
}
}
extension UITextField
{
func setBottomBorder(color:CGColor)
{
if let isBottomBorder = self.getBottomBorderIfExists() {
isBottomBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.bottonBorderFrame(self), color: color, andKey: MyBottomBorder)
}
func setTopBorder(color:CGColor)
{
if let isTopBorder = self.getTopBorderIfExists() {
isTopBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.topBorderFrame(self), color: color, andKey: MyTopBorder)
}
func setBorderWithFrame(frame: CGRect, color: CGColor, andKey: String) {
self.borderStyle = UITextBorderStyle.None;
let border = CALayer()
border.borderColor = color
border.frame = frame
border.borderWidth = Defaults.width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
self.layer.setValue(border, forKey: andKey)
}
func removeTopBorder() {
if let isTopBorder = self.getTopBorderIfExists() {
self.layer.setValue(nil, forKey: MyTopBorder)
isTopBorder.removeFromSuperlayer()
}
}
func removeBottomBorder() {
if let isBottomBorder = self.getBottomBorderIfExists() {
self.layer.setValue(nil, forKey: MyBottomBorder)
isBottomBorder.removeFromSuperlayer()
}
}
private func getBorderIfExistsByKey(key: String)->CALayer? {
if let isBorderSet = self.layer.valueForKey(key) {
if let borderIsCALayer = isBorderSet as? CALayer {
return borderIsCALayer
}
}
return nil
}
private func getTopBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyTopBorder)
}
private func getBottomBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyBottomBorder)
}
public override func layoutSubviews() {
super.layoutSubviews()
// Update bottom on frame change
if let isBottomBorder = self.getBottomBorderIfExists() {
let borderColor = isBottomBorder .borderColor
self.removeBottomBorder()
self.setBottomBorder(borderColor!)
}
// Update top on frame change
if let isTopBorder = self.getTopBorderIfExists() {
let borderColor = isTopBorder.borderColor
self.removeTopBorder()
self.setTopBorder(borderColor!)
}
}
}
Usage:
let textField = UITextField(frame: CGRect(x: 100,y: 100, width: 100, height: 100))
textField.backgroundColor = UIColor.blueColor() // Thie color is for visulizing better
self.view.addSubview(textField)
textField.setBottomBorder(UIColor.blackColor().CGColor) // Now you have a border
textField.frame = CGRect(x: 150, y: 200, width: 200, height: 200) // And the border updated to the new frame
// Now if you would like to change from bottom to top, simply do this:
textField.removeBottomBorder()
textField.setTopBorder(UIColor.blackColor().CGColor)

UIPageControl custom class - found nil changing image to the dots

I need to implement a UIPageControl with custom images instead the normal dot. So I create a custom class and connect it through the storyboard.
Since ios7 the subview of UIPageControl contain UIView instead of UIImageView. The subviews of the resulting UIView(UIIpageControl subviews) doesn't contain any subviews so I receive the error:
fatal error: unexpectedly found nil while unwrapping an Optional value.
Where I might have been wrong?
class WhitePageControl:UIPageControl{
let activeImage = UIImage(named: "dot_white")
let inactiveImage = UIImage(named: "dot_white_e")
override init(frame: CGRect){
super.init(frame: frame)
}
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
func updateDots(){
println(self.subviews.count) // 3
println(self.numberOfPages) // 3
for var index = 0; index < self.subviews.count; index++ {
println(index)
var dot:UIImageView!
var dotView:UIView = self.subviews[index] as UIView
println("1")
for subview in dotView.subviews{ // NIL HERE
println("2")
if subview.isKindOfClass(UIImageView){
println("3")
dot = subview as UIImageView
if index == self.currentPage{ dot.image = activeImage }
else{ dot.image = inactiveImage }
}
}
}
}
func setCurrentPage(value:Int){
super.currentPage = value
self.updateDots()
}
}
It's my solution:
import Foundation
class PageControl: UIPageControl {
var activeImage: UIImage!
var inactiveImage: UIImage!
override var currentPage: Int {
//willSet {
didSet { //so updates will take place after page changed
self.updateDots()
}
}
convenience init(activeImage: UIImage, inactiveImage: UIImage) {
self.init()
self.activeImage = activeImage
self.inactiveImage = inactiveImage
self.pageIndicatorTintColor = UIColor.clearColor()
self.currentPageIndicatorTintColor = UIColor.clearColor()
}
func updateDots() {
for var i = 0; i < count(subviews); i++ {
var view: UIView = subviews[i] as! UIView
if count(view.subviews) == 0 {
self.addImageViewOnDotView(view, imageSize: activeImage.size)
}
var imageView: UIImageView = view.subviews.first as! UIImageView
imageView.image = self.currentPage == i ? activeImage : inactiveImage
}
}
// MARK: - Private
func addImageViewOnDotView(view: UIView, imageSize: CGSize) {
var frame = view.frame
frame.origin = CGPointZero
frame.size = imageSize
var imageView = UIImageView(frame: frame)
imageView.contentMode = UIViewContentMode.Center
view.addSubview(imageView)
}
}
change setCurrentPage to Current Page ,bcoz that one is Obj C property
func currentPage(page: Int) {
super.currentPage = page
self.updateDots()
}
try this.

Resources