Custom control exceeding bound of Stackview cell - ios

As per the image below, a custom control with an embedded UITextView will jump out of its bounds. I've tried applying clip to bounds etc. to no avail. Probably something simple when establishing a custom control.
Here is the code for the ViewController.
Result is as follows
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textStackView: UIStackView!
#IBOutlet weak var tabStackView: UIStackView!
override func viewDidLoad() {
super.viewDidLoad()
addButtons()
addTextViews()
}
func addButtons(){
var count = 0
while (count <= 10){
let btn = UIButton()
btn.backgroundColor = UIColor.gray
btn.setTitle("Btn \(count)", for: .normal)
tabStackView.addArrangedSubview(btn)
count += 1
}
}
func addTextViews(){
var count = 0
while (count <= 5){
if (count == 0){
let textView = CustomView()
textView.layer.borderColor = UIColor.green.cgColor //Green border
textView.layer.borderWidth = 1
textView.embededTextView.text = "Some sample text here. Some sample text here. Some sample text here. Some sample text here. Some sample text here."
//textView.sizeToFit() //Does nothing
//textView.clipsToBounds = true //Doesnt prevent overflow
textStackView.addArrangedSubview(textView)
}
else{
let textView = UITextView()
textView.layer.borderColor = UIColor.black.cgColor
textView.layer.borderWidth = 1
textView.text = "Some sample text here. Some sample text here. Some sample text here. Some sample text here. Some sample text here."
textStackView.addArrangedSubview(textView)
}
count += 1
}
}
}
and the custom control
import UIKit
class CustomView: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var embededTextView: UITextView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
self.addSubview(self.view)
}
override init(frame: CGRect){
super.init(frame: frame)
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
self.addSubview(self.view)
view.layer.borderWidth = 4
view.layer.borderColor = UIColor.red.cgColor
//view.sizeToFit()
}
}

What was missing was
view.frame = self.bounds
in
override init(frame: CGRect){
super.init(frame: frame)
Bundle.main.loadNibNamed("CustomView", owner: self, options: nil)
view.frame = self.bounds
view.layer.borderWidth = 4
view.layer.borderColor = UIColor.red.cgColor
self.addSubview(self.view)
}

Related

How to make a button work on a custom view from .xib instantiated programmatically

The button works if I add the view via the Interface Builder but it doesn't work when I add the view programmatically.
.xib design:
My custom view class:
class customView: UIView {
static let singleton1 = customView()
#IBOutlet weak var newView: UIView!
#IBAction func changeColor(_ sender: Any) {
newView.backgroundColor = UIColor.systemBlue // this is what the button should do
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
func commonInit() {
let viewFromXib = Bundle.main.loadNibNamed("customView", owner: self, options: nil)![0] as! UIView
viewFromXib.bounds = self.bounds
addSubview(viewFromXib)
}
This is the way I instantiate it:
#IBAction func showView(_ sender: Any) {
let playerview = customView.singleton1
view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(playerview)
playerview.tag = 100
UIApplication.shared.keyWindow?.addSubview(playerview)// yes it needs to be in the window
Not sure if my initializer is wrong or something else.
You are trying to load "customView" xib into its own init method. Try below.
Replace commonInit() method with getView(frame:CGRect)->UIView
class customView: UIView {
static let singleton1 = customView()
#IBOutlet weak var newView: UIView!
#IBAction func changeColor(_ sender: Any) {
newView.backgroundColor = UIColor.systemBlue // this is what the button should do
}
override init(frame: CGRect) {
super.init(frame: frame)
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
window?.windowLevel = UIWindow.Level(rawValue: CGFloat.greatestFiniteMagnitude)
}
func getView(frame:CGRect)->UIView{
let viewFromXib = Bundle.main.loadNibNamed("customView", owner: nil, options: nil)![0] as! UIView
viewFromXib.frame = frame
return viewFromXib
}
}
Replace showView() method
#IBAction func showView(_ sender: Any) {
let playerview = customView.singleton1.getView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: 200))
self.view.addSubview(playerview)
playerview.tag = 100
UIApplication.shared.keyWindow?.addSubview(playerview)
}

How to change color only a fraction of the total in swift?

I'm doing a QR code scan. I use UIView to change the background color of the screen to translucent color. However, I want to make the background color of the place that scans the QR code transparent. What should I do?
class QRcodeScannerViewController : UIViewController, AVCaptureMetadataOutputObjectsDelegate {
#IBOutlet weak var qrcodeView: UIView!
#IBOutlet weak var header: UINavigationBar!
#IBOutlet weak var flash: UIButton!
#IBOutlet weak var qrcodeScanArea: UIImageView!
var previewLayer: AVCaptureVideoPreviewLayer!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
self.qrcodeView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
view.layer.insertSublayer(previewLayer, at: 0)
view.bringSubviewToFront(flash)
view.bringSubviewToFront(header)
header.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
header.isTranslucent = true
header.backgroundColor = UIColor.clear
header.shadowImage = UIImage()
Current My QRcode ScanView
Here's the view I want:
I don't know which part I reverse, but only the color I want is black and the rest is transparent.
view.addSubview(qrcodeView)
let shape = CGRect(x: 0, y: 0, width: 200, height: 200)
let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(roundedRect: shape, cornerRadius: 0).cgPath
maskLayer.backgroundColor = UIColor.clear.cgColor
maskLayer.fillRule = CAShapeLayerFillRule.evenOdd
qrcodeView.layer.mask = maskLayer
I made and applied the class by referring to the answers below. But it doesn't work for me.
let focusview = FocusView.init(frame: qrcodeView.frame)
focusview.areaBackground = UIColor.black
view.addSubview(focusview)
I did something similar ... actually pretty much the same thing, some time ago.
I've dug out the code I used and have posted it here for your reference
fileprivate let focusSize: CGSize = CGSize(width: 218, height: 150)
fileprivate let focusCornerRadius: CGFloat = 10.0
class FocusView: UIView {
var areaBackground: UIColor? {
set {
maskedFocusView.backgroundColor = newValue
}
get {
return maskedFocusView.backgroundColor
}
}
fileprivate let maskedFocusView: MaskedFocusView = {
return MaskedFocusView()
}()
let focusView: UIView = {
let view = UIView()
view.layer.borderColor = UIColor.white.cgColor
view.layer.borderWidth = 2
view.layer.cornerRadius = focusCornerRadius
// view.layer.shadowColor = UIColor.white.cgColor
// view.layer.shadowRadius = 5.0
// view.layer.shadowOpacity = 0.9
// view.layer.shadowOffset = CGSize.zero
view.layer.masksToBounds = true
return view
}()
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
backgroundColor = UIColor.darkGray.withAlphaComponent(0.5)
translatesAutoresizingMaskIntoConstraints = false
addSubview(maskedFocusView)
addSubview(focusView)
setupFocusViewConstraints()
setupMaskedFocusViewConstraints()
}
func setupFocusViewConstraints() {
NSLayoutConstraint.activate(
focusView.centerXAnchor.constraint(equalTo: centerXAnchor),
focusView.centerYAnchor.constraint(equalTo: centerYAnchor)
)
let regularFocusViewConstraints = [
focusView.widthAnchor.constraint(equalToConstant: focusSize.width),
focusView.heightAnchor.constraint(equalToConstant: focusSize.height)
]
NSLayoutConstraint.activate(regularFocusViewConstraints)
}
func setupMaskedFocusViewConstraints() {
NSLayoutConstraint.activate(
maskedFocusView.centerXAnchor.constraint(equalTo: centerXAnchor),
maskedFocusView.centerYAnchor.constraint(equalTo: centerYAnchor),
maskedFocusView.topAnchor.constraint(equalTo: topAnchor),
maskedFocusView.bottomAnchor.constraint(equalTo: bottomAnchor),
maskedFocusView.leadingAnchor.constraint(equalTo: leadingAnchor),
maskedFocusView.trailingAnchor.constraint(equalTo: trailingAnchor)
)
}
}
// MARK: - Masked focus view
fileprivate class MaskedFocusView: UIView {
let maskLayer: CAShapeLayer = CAShapeLayer()
override func awakeFromNib() {
super.awakeFromNib()
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
func commonInit() {
backgroundColor = UIColor.darkGray.withAlphaComponent(0.5)
maskLayer.backgroundColor = UIColor.clear.cgColor
layer.mask = maskLayer
translatesAutoresizingMaskIntoConstraints = false
}
override func layoutSubviews() {
super.layoutSubviews()
let width = bounds.width
let height = bounds.height
let x = (width - focusSize.width) / 2
let y = (height - focusSize.height) / 2
let focusRect = CGRect(x: x, y: y, width: focusSize.width, height: focusSize.height)
let fullRect = CGRect(origin: bounds.origin, size: bounds.size)
let path = CGMutablePath()
path.addPath(UIBezierPath(rect: fullRect).cgPath)
path.addPath(UIBezierPath(roundedRect: focusRect, cornerRadius: focusCornerRadius).reversing().cgPath)
maskLayer.path = path
maskLayer.fillRule = .evenOdd
}
}
// MARK: - Layout constraints extension
extension NSLayoutConstraint {
/// A helper function to activate layout constraints.
static func activate(_ constraints: NSLayoutConstraint? ...) {
for case let constraint in constraints {
guard let constraint = constraint else {
continue
}
(constraint.firstItem as? UIView)?.translatesAutoresizingMaskIntoConstraints = false
constraint.isActive = true
}
}
}
extension Array where Element: NSLayoutConstraint {
func activate() {
forEach {
if !$0.isActive {
$0.isActive = true
}
}
}
func deactivate() {
forEach {
if $0.isActive {
$0.isActive = false
}
}
}
}

Select and deselect reusable views

I create a custom view in xib file. I add 3 views(which inherited from custom) in viewController. Initially they have white color, but when i click on first view it should be changed other color and if i click on second view, the first view should be back in white color.
I need help to change first view color back to white when second view is selected.
My code for customView here
class SubscriptionView: UIView {
#IBOutlet weak var title: UILabel!
#IBOutlet weak var subTitle: UILabel!
#IBOutlet weak var checkMark: UIImageView!
var isSelect: Bool = false
let nibName = "SubscriptionView"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func awakeFromNib() {
super.awakeFromNib()
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapAction)))
}
func commonInit() {
guard let view = loadViewFromNib() else {
return
}
view.frame = self.bounds
view.layer.masksToBounds = true
view.layer.cornerRadius = 14
view.layerBorderColor = AppColor.amaranth
view.layerBorderWidth = 0.5
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func selectedView(_ isSelect: Bool) {
self.isSelect = isSelect
title.textColor = isSelect ? UIColor.white : AppColor.amaranth
subTitle.textColor = isSelect ? UIColor.white : AppColor.amaranth
checkMark.alpha = isSelect ? 1.0 : 0.0
contentView!.backgroundColor = isSelect ? AppColor.amaranth : UIColor.white
}
#objc private func tapAction() {
///????? selectedView
}
}
Here is a very simple example using the Delegate / Protocol pattern.
Define a Protocol:
// Protocol / Delegate pattern
protocol SubscriptionViewDelegate {
func gotTap(from sender: SubscriptionView)
}
Have your view controller conform to that protocol. In your custom SubscriptionView class, when the user taps you will tell the delegate that a tap was received, and the controller will loop through the SubscriptionView objects setting the "selected" state to true or false, based on the tapped view.
class SubscriptionsViewController: UIViewController, SubscriptionViewDelegate {
let theStackView: UIStackView = {
let v = UIStackView()
v.translatesAutoresizingMaskIntoConstraints = false
v.axis = .vertical
v.alignment = .fill
v.distribution = .fill
v.spacing = 20
return v
}()
// array to track the "subscription" views
var arrayOfSubscriptionViews: [SubscriptionView] = [SubscriptionView]()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .yellow
// add a stack view to hold the "subscription" views
view.addSubview(theStackView)
NSLayoutConstraint.activate([
theStackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20.0),
theStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20.0),
theStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20.0),
])
// instantiate 3 "subscription" views
for _ in 1...3 {
// instantiate view
let v = SubscriptionView()
// set self as its delegate
v.delegate = self
// add it to our stack view
theStackView.addArrangedSubview(v)
// append it to our tracking array
arrayOfSubscriptionViews.append(v)
}
}
func gotTap(from sender: SubscriptionView) {
// just for dev / debugging
print("got tap from", sender)
// loop through the subscription views,
// setting the sender selected to TRUE
// the others to FALSE
arrayOfSubscriptionViews.forEach {
$0.selectedView($0 == sender)
}
}
}
// Protocol / Delegate pattern
protocol SubscriptionViewDelegate {
func gotTap(from sender: SubscriptionView)
}
class SubscriptionView: UIView {
#IBOutlet weak var title: UILabel!
#IBOutlet weak var subTitle: UILabel!
#IBOutlet weak var checkMark: UIImageView!
var isSelect: Bool = false
var delegate: SubscriptionViewDelegate?
let nibName = "SubscriptionView"
var contentView: UIView?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
override func awakeFromNib() {
super.awakeFromNib()
}
func commonInit() {
guard let view = loadViewFromNib() else {
return
}
view.frame = self.bounds
view.layer.masksToBounds = true
view.layer.cornerRadius = 14
view.layer.borderColor = UIColor.red.cgColor // AppColor.amaranth
view.layer.borderWidth = 0.5
self.addSubview(view)
contentView = view
selectedView(false)
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tapAction)))
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
public func selectedView(_ isSelect: Bool) {
self.isSelect = isSelect
title.textColor = isSelect ? UIColor.white : .red // AppColor.amaranth
subTitle.textColor = isSelect ? UIColor.white : .red // AppColor.amaranth
checkMark.alpha = isSelect ? 1.0 : 0.0
// contentView!.backgroundColor = isSelect ? AppColor.amaranth : UIColor.white
contentView!.backgroundColor = isSelect ? UIColor.red : UIColor.white
}
#objc private func tapAction() {
// for dev / debugging
print("sending tap from", self)
// tell the delegate self got tapped
delegate?.gotTap(from: self)
}
}
I only made minor changes to your SubscriptionView class, so you should be able to use it as-is with your existing .xib

XIB view not showing with correct layout [iOS Swift]

I'm trying to make a custom alert view and facing some issues with the fact that the displayed view is cutting the bottom half of the view (Images below)
How it's being displayed:
Desired Output:
So basically, I have a XIB called CustomAlertView supported by a class of same name with init as follows:
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
private func commonInit() {
Bundle.main.loadNibNamed("CustomAlertView", owner: self, options: nil)
contentView.frame = self.bounds
contentView.translatesAutoresizingMaskIntoConstraints = true
addSubview(contentView)
//contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
I have another class that is responsible for creating an alert, CustomAlert, using the customAlertView. This CustomAlert class is creating the backgroundView and dialogView( which I'm trying to add my customAlertView to it) with the following code:
func initialize(title:String, description:String){
dialogView.clipsToBounds = true
backgroundView.frame = frame
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.6
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTappedOnBackgroundView)))
addSubview(backgroundView)
dialogView.frame.origin = CGPoint(x: 0, y: 0)
dialogView.frame.size = CGSize(width: frame.width-32, height: frame.height/3)
dialogView.backgroundColor = UIColor.white
dialogView.layer.cornerRadius = 6
let alertView = CustomAlertView.init(frame: self.bounds)
alertView.titleLabel.text = title
alertView.descriptionLabel.text = description
alertView.cancelButton.backgroundColor = UIColor.brown
dialogView.addSubview(alertView)
addSubview(dialogView)
}
I believe that I'm making a confusion with the frames and bounds but couldn't find a solution.
I'd like the desired output to be placed perfectly inside the dialogView.
EDIT
Code for my .show function in CustomAlert
func show(animated:Bool){
self.backgroundView.alpha = 0
self.dialogView.center = CGPoint(x: self.center.x, y: self.frame.height + self.dialogView.frame.height/2)
UIApplication.shared.delegate?.window??.rootViewController?.view.addSubview(self)
if animated {
UIView.animate(withDuration: 0.33, animations: {
self.backgroundView.alpha = 0.66
})
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 10, options: UIViewAnimationOptions(rawValue: 0), animations: {
self.dialogView.center = self.center
}, completion: { (completed) in
})
}else{
self.backgroundView.alpha = 0.66
self.dialogView.center = self.center
}
}
Github link git-alert-view
For anyone facing the same difficulties as me, I was able to accomplish the wanted result.
I used AutoLayouts as suggested by #HAK. But instead of writing my own NSLayoutConstraint I used roberthein library called TinyConstraints.
Basically, I used as follow:
Instead of
NSLayoutConstraint.activate([
alertView.topAnchor.constraint(equalTo: superview.topAnchor, constant: 0),
alertView.leadingAnchor.constraint(equalTo: superview.leadingAnchor, constant: 0),
alertView.bottomAnchor.constraint(equalTo: superview.bottomAnchor, constant: 0),
alertView.trailingAnchor.constraint(equalTo: superview.trailingAnchor, constant:
0)])
with TinyConstraints:
alertView.edges(to: superview)
That´s it
Change your CustomAlertView like this:
class CustomAlertView: UIView {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var confirmButton: UIButton!
#IBOutlet weak var cancelButton: UIButton!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
static func customAlert() -> CustomAlertView {
return Bundle.main.loadNibNamed("CustomAlertView", owner: self, options: nil)!.first as! CustomAlertView
}
}
Your CustomAlert's initialize method like this:
func initialize(title:String, description:String){
dialogView.clipsToBounds = true
backgroundView.frame = frame
backgroundView.backgroundColor = UIColor.black
backgroundView.alpha = 0.6
backgroundView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTappedOnBackgroundView)))
addSubview(backgroundView)
dialogView.frame.origin = CGPoint(x: 0, y: 0)
dialogView.frame.size = CGSize(width: frame.width-32, height: frame.height/3)
dialogView.backgroundColor = UIColor.white
dialogView.layer.cornerRadius = 6
let alertView = CustomAlertView.customAlert()
alertView.titleLabel.text = title
alertView.descriptionLabel.text = description
alertView.cancelButton.backgroundColor = UIColor.brown
dialogView.addSubview(alertView)
addSubview(dialogView)
}
In the CustomAlertView xib:
1. Select fileowner and remove the class (default to NSObject).
2. Select fileowner and then remove all the outlets.
3. Select your content view and give it class = CustomAlertView.
4. Select your CustomAlertView and make all outlet connections.
Final Xib:
And you have a working alert:
PS: Adjust the UI accordingly.
In your xib class add this :
override func awakeFromNib() {
super.awakeFromNib()
xibSetup()}
func xibSetup() {
guard let view = loadViewFromNib() else { return }
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
addSubview(view)
contentView = view
}
Reference from this : https://medium.com/zenchef-tech-and-product/how-to-visualize-reusable-xibs-in-storyboards-using-ibdesignable-c0488c7f525d#.3c0javomy**

Can't set property of a UIView class

I have a UIView class:
class FlashCard: UIView {
var linkedMemory: Memory? {
didSet {
frontView = showContent(of: linkedMemory!.front)
backView = showContent(of: linkedMemory!.back)
}
}
var frontView = UIView()
var backView = UIView()
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
func showContent(of linkedMemory: Any) -> UIView {
var contentView = UIView()
if let image = linkedMemory as? UIImage {
let imageView = UIImageView()
imageView.image = image
contentView = imageView
}
if let text = linkedMemory as? String {
let label = UILabel()
label.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
label.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
label.text = text
contentView = label
}
return contentView
}
}
I'm trying to set its linkedMemory property in a view controller that contains this view:
#IBOutlet weak var flashCardView: FlashCard!
override func viewDidLoad() {
calcGroupFamiliarity()
flashCardView.linkedMemory = Memory(masteryLevel: 1, algorithm: Algorithm.algorithm1.chooseAlgorithm(), forgetRatio: 0, lastStudyTime: Date(), front: #imageLiteral(resourceName: "Ideas-Blue"), back: #imageLiteral(resourceName: "Ideas-Yellow"))
// See? I'm trying to change flashCardView's property here, but it never changes :(
}
Anybody has any idea on how to solve this?

Resources