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

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)
}

Related

How to change navigationBar height & use custom view

I have a custom navigationBar view like this.
And I use sizeThatFits seems not work.
How to achieve this?
class ListVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let height: CGFloat = 140
let bounds = self.navigationController?.navigationBar.bounds
self.navigationController?.navigationBar.frame = CGRect(x: 0, y: 0, width: bounds?.width ?? UIScreen.main.bounds.width, height: height)
}
}
class CustomNavi: UINavigationBar {
override init(frame: CGRect) {
super.init(frame: frame)
self.commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
self.commonInit()
}
override func sizeThatFits(_ size: CGSize) -> CGSize {
return CGSize(width: UIScreen.main.bounds.width, height: 140)
}
private func commonInit() {
let bundle = Bundle.init(for: CustomNavi.self)
if let viewToAdd = bundle.loadNibNamed("CustomNavi", owner: self, options: nil), let contentView = viewToAdd.first as? UIView {
addSubview(contentView)
contentView.frame = self.bounds
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
}

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

Swift: Use of unresolved identifier 'present'

I have a UIView added to UIViewController, and some code of them
ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let listView = ListView()
self.view.addSubview(listView)
}
ListView.swift
class ListView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(btnGO)
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
lazy var btnGO:(UIButton) = {
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 34))
btn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)
//some codes
return btn
}()
#objc func btnClicked(sender:UIButton) {
let shareWOW = "some content"
let objectsToShare = [shareWOW]
let activityController = UIActivityViewController(
activityItems: objectsToShare,
applicationActivities: nil)
activityController.popoverPresentationController?.sourceRect = self.frame
activityController.popoverPresentationController?.sourceView = self
activityController.popoverPresentationController?.permittedArrowDirections = .any
present(activityController, animated: true, completion: nil)
}
}
I always get error information Use of unresolved identifier 'present', and I know UIActivityViewController should be placed in UIViewController, and my question is I place the function btnClicked in ViewController.swift, how to call this function in a UIView as ListView.swift? Many thanks
present(_:animated:completion:) method should be used with UIViewController instance. You can't call this method in a UIView subclass. Use delegate to call a method in ViewController from ListView and present UIActivityViewController from the ViewController like this
ViewController.swift
class ViewController: UIViewController, ListViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let listView = ListView()
listView.delegate = self
self.view.addSubview(listView)
}
func btnClicked(sender: UIButton) {
let shareWOW = "some content"
let objectsToShare = [shareWOW]
let activityController = UIActivityViewController(
activityItems: objectsToShare,
applicationActivities: nil)
activityController.popoverPresentationController?.sourceRect = sender.frame
activityController.popoverPresentationController?.sourceView = sender
activityController.popoverPresentationController?.permittedArrowDirections = .any
self.present(activityController, animated: true, completion: nil)
}
}
ListView.swift
protocol ListViewDelegate {
func btnClicked(sender:UIButton)
}
class ListView: UIView {
var delegate: ListViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(btnGO)
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
lazy var btnGO:(UIButton) = {
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 34))
btn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)
//some codes
return btn
}()
#objc func btnClicked(sender:UIButton) {
delegate?.btnClicked(sender: sender)
}
}

iOS - How to initialize custom UIView with specific Frame from NIB

I am wondering what is the cleanest way to initialize a custom UIView with a specific frame.
The UIView is designed from a XIB file.
Here is my implementation :
class CustomView : UIView {
#IBOutlet var outletLabel: UILabel!
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
public override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
private func setupView() {
// Set text for labels
}
}
Here is how I want to initialize it in my ViewController :
let screenSize: CGRect = UIScreen.main.bounds
let screenWidth = screenSize.width
let frame = CGRect(x: 0, y: 0, width: screenWidth - 50, height: 70)
let customView = CustomView.init(frame: frame)
But it is not working, I have a white UIView without any outlets.
And if I do this instead :
// Extension to UIView to load Nib
let customView : CustomView = UIView.fromNib()
I can see my view from XIB file, with its width/height used in the Interface Builder.
What is I want to load the view from XIB file BUT with specific frame ?
Am I missing something about initialization ?
You can have a NibLoading class like:
// NibLoadingView.swift
//source:https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8
import UIKit
// Usage: Subclass your UIView from NibLoadView to automatically load a xib with the same name as your class
#IBDesignable
class NibLoadingView: UIView {
#IBOutlet weak var view: UIView!
override init(frame: CGRect) {
super.init(frame: frame)
nibSetup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
nibSetup()
}
private func nibSetup() {
backgroundColor = .clear
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.translatesAutoresizingMaskIntoConstraints = true
addSubview(view)
}
private func loadViewFromNib() -> UIView {
let bundle = Bundle(for: type(of:self))
let nib = UINib(nibName: String(describing: type(of:self)), bundle: bundle)
let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView
nibView.anchorAllEdgesToSuperview()
return nibView
}
}
extension UIView {
func anchorAllEdgesToSuperview() {
self.translatesAutoresizingMaskIntoConstraints = false
if #available(iOS 9.0, *) {
addSuperviewConstraint(constraint: topAnchor.constraint(equalTo: (superview?.topAnchor)!))
addSuperviewConstraint(constraint: leftAnchor.constraint(equalTo: (superview?.leftAnchor)!))
addSuperviewConstraint(constraint: bottomAnchor.constraint(equalTo: (superview?.bottomAnchor)!))
addSuperviewConstraint(constraint: rightAnchor.constraint(equalTo: (superview?.rightAnchor)!))
}
else {
for attribute : NSLayoutAttribute in [.left, .top, .right, .bottom] {
anchorToSuperview(attribute: attribute)
}
}
}
func anchorToSuperview(attribute: NSLayoutAttribute) {
addSuperviewConstraint(constraint: NSLayoutConstraint(item: self, attribute: attribute, relatedBy: .equal, toItem: superview, attribute: attribute, multiplier: 1.0, constant: 0.0))
}
func addSuperviewConstraint(constraint: NSLayoutConstraint) {
superview?.addConstraint(constraint)
}
}
Then your view will subclass the NibLoadingClass like:
class YourUIView: NibLoadingView {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Set your XIB class in File's Owner like:
In this case it will be YourUIView
Then instantiate it:
let myView = YourUIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.size.width-60, height: 170))
You can create custom class like this...
import UIKit
class CustomView: UIView {
class func mainView() -> CustomView {
let nib = UINib(nibName: "nib", bundle: nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! CustomView
return view
}
override init(frame: CGRect) {
super.init(frame: frame)
let view = CustomView.mainView()
view.frame = frame
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Initialize view where you want...
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = UIColor.blue
self.view.addSubview(view)

UISearchBar's text move to bottom when input content?

I customized a UISearchbar, the code is like this:
class CustomSearchBar: UIView {
lazy var searchBar: MySearchBar = {
let search = MySearchBar()
search.tintColor = UIColor.orange
search.barTintColor = UIColor.yellow
search.searchBarStyle = .minimal
search.searchTextPositionAdjustment = UIOffset(horizontal: 10, vertical: 0)
return search
}()
lazy var cancelButton: UIButton = {
let button = UIButton(type: .custom)
button.setTitle("取消", for: .normal)
button.setTitleColor(UIColor.black, for: .normal)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
setupConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
addSubview(searchBar)
addSubview(cancelButton)
}
private func setupConstraints() {
searchBar.snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(20)
make.top.equalToSuperview().offset(6)
make.height.equalTo(32)
make.width.equalToSuperview().offset(-80)
}
cancelButton.snp.makeConstraints { (make) in
make.left.equalTo(searchBar.snp.right).offset(13)
make.centerY.equalTo(searchBar)
}
}
}
class MySearchBar: UISearchBar {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func indexOfSearchFieldInSubviews() -> Int! {
var index: Int!
let searchBarView = subviews[0]
for (i, view) in searchBarView.subviews.enumerated() {
if view.isKind(of: UITextField.self) {
index = i
break
}
}
return index
}
override func draw(_ rect: CGRect) {
if let index = indexOfSearchFieldInSubviews() {
let searchField: UITextField = subviews[0].subviews[index] as! UITextField
searchField.leftView = UIImageView(image: UIImage(named: "Search_icon"))
searchField.font = UIFont.systemFont(ofSize: 14)
searchField.borderStyle = .none
searchField.layer.masksToBounds = true
searchField.layer.cornerRadius = 4
searchField.backgroundColor = barTintColor
searchField.contentVerticalAlignment = .center
searchField.placeholder = "input something"
}
}
}
we can see that it is very simple. Just added a searchBar and cancelButton.
Then I added it to navigationBar as subView, make it becomeFirstResponder in viewController.
class ViewController: UIViewController {
lazy var customeSearchBar = CustomSearchBar()
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.navigationBar.addSubview(customeSearchBar)
customeSearchBar.snp.makeConstraints { (make) in
make.left.right.equalToSuperview()
make.centerY.equalToSuperview()
make.height.equalTo(44)
}
customeSearchBar.searchBar.becomeFirstResponder()
}
}
At first, I input content is ok.
When I continue input content, then the issue shows up:
It looks like the content move to the bottom. I tried to find something to fix the problem. But I didn't get it.
It is a strange thing that works fine in simulate and some iPhone. But one of my phone works wrong.

Resources