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
Related
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)
}
Encountered a problem: largeTitle of navigation bar collapsing to small title ~once per 20 attempts after refreshControl.endRefreshing().
tableView is first subview of subviews hierarchy, and top constraint is to superview (not to safeArea)
refreshControl added to tableView.refreshControl, not as subview of tableView
was trying call navigationBar.sizeToFit() after endRefreshing - not success.
Controller code:
override public func viewDidLoad() {
super.viewDidLoad()
definesPresentationContext = true
title = L10n.Trainers.trainers.uppercased()
navigationController?.navigationBar.layoutMargins = .init(top: 0, left: 16, bottom: 0, right: 16)
configureSubviews()
makeConstraints()
configureBindings()
configureActions()
setupTableView()
setupResultsTableView()
}
override public func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.prefersLargeTitles = true
navigationController?.navigationItem.largeTitleDisplayMode = .always
navigationController?.navigationBar.sizeToFit()
}
override public func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
navigationController?.navigationItem.largeTitleDisplayMode = .automatic
}
override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
vm.onViewDidAppear()
mainView.tableView.refreshControl = mainView.refreshControl
}
override public func loadView() {
view = mainView
}
Refresh-logic code:
vm.isLoading.drive { [weak self] loading in
guard let self = self else { return }
if self.mainView.refreshControl.isRefreshing {
if !loading {
self.mainView.tableView.refreshControl?.endRefreshing()
}
} else {
if loading {
var style = LoaderParameters.blue
style.installConstraints = false
self.mainView.tableView.startLoading(with: style)
self.mainView.tableView.existedLoaderView?.centerX().centerY(-40)
} else {
self.mainView.tableView.stopLoadingProgress()
}
}
}.store(in: &subscriptions)
View code:
final class TrainersView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
configureSubviews()
makeConstraints()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
private(set) lazy var refreshControl = UIRefreshControl().configureWithAutoLayout {
$0.tintColor = Asset.mainGrey.color
}
private(set) lazy var topView: UIView = {
let view = UIView()
view.backgroundColor = .clear
return view
}()
private(set) lazy var tableView = UITableView().configureWithAutoLayout {
$0.register(TrainerCell.self)
$0.tableFooterView = UIView()
$0.showsVerticalScrollIndicator = false
$0.separatorStyle = .none
$0.backgroundColor = Asset.mainWhite.color
}
private func configureSubviews() {
addSubview(tableView)
topView.addSubview(segmentedControl)
tableView.tableHeaderView = topView
tableView.tableHeaderView?.frame.size.height = 60
tableView.tableFooterView?.frame.size.height = 40
tableView.reloadData()
}
private func makeConstraints() {
tableView.pinToSuperview()
segmentedControl.centerX()
segmentedControl.bottomAnchor.constraint(equalTo: topView.bottomAnchor, constant: -8).priority(999).activate()
segmentedControl.widthAnchor ~ widthAnchor - 16 * 2
}
}
I want to ask you, is there any way to make a swift button has the design like this when clicked, and this when not. I want to give me some proposal or anything to do it.
Create a Custom UIView Class and copy the below code and try it :)
Don't forget to change a couple of values for customizations
class TestView: UIView {
private let selectorView: UIView = UIView()
private let trackView: UIView = UIView()
private var selectorViewLeadingConstraint: NSLayoutConstraint!
private var selectorViewTrailingConstraint: NSLayoutConstraint!
private var tapGesture: UITapGestureRecognizer!
public var isSelected: Bool = false {
didSet {
self.setIsSelected(isSelected)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
addTrackView()
addSelectorView()
addTapGestures()
}
private func addTapGestures() {
tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapHandler))
addGestureRecognizer(tapGesture)
}
private func addTrackView() {
addSubview(trackView)
trackView.translatesAutoresizingMaskIntoConstraints = false
trackView.layer.cornerRadius = 5
trackView.backgroundColor = .gray
NSLayoutConstraint.activate([
trackView.leadingAnchor.constraint(equalTo: leadingAnchor),
trackView.trailingAnchor.constraint(equalTo: trailingAnchor),
trackView.heightAnchor.constraint(equalToConstant: 10),
trackView.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
private func addSelectorView(){
addSubview(selectorView)
selectorView.translatesAutoresizingMaskIntoConstraints = false
selectorView.backgroundColor = .green
selectorViewLeadingConstraint = selectorView.leadingAnchor.constraint(equalTo: leadingAnchor)
selectorViewLeadingConstraint.priority = UILayoutPriority(500)
selectorViewTrailingConstraint = selectorView.trailingAnchor.constraint(equalTo: trailingAnchor)
selectorViewTrailingConstraint.priority = .defaultLow
NSLayoutConstraint.activate([
selectorViewLeadingConstraint,
selectorViewTrailingConstraint,
selectorView.topAnchor.constraint(equalTo: topAnchor),
selectorView.bottomAnchor.constraint(equalTo: bottomAnchor),
selectorView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5),
])
}
override func layoutSubviews() {
super.layoutSubviews()
selectorView.layer.cornerRadius = selectorView.frame.height/2
}
#objc func tapHandler() {
isSelected = !isSelected
}
private func setIsSelected(_ isSelected: Bool) {
selectorViewTrailingConstraint.priority = isSelected ? .defaultHigh : .defaultLow
UIView.animate(withDuration: 0.3) {
self.layoutIfNeeded()
self.selectorView.backgroundColor = isSelected ? .green : .green
}
}
}
I am trying to implement a simple UIView that will show at the bottom of the screen when internet connection is lost and will have a dismiss button. My UIView class looks something like this.
import UIKit
class ConnectionLostAlertUIView: UIView {
#IBOutlet var view: UIView!
var viewConstraints: [NSLayoutConstraint] = []
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
}
override func didMoveToSuperview() {
view.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = view.bottomAnchor.constraint(equalTo: self.superview!.bottomAnchor)
let leftConstraint = view.leadingAnchor.constraint(equalTo: self.superview!.leadingAnchor)
let rightConstraint = view.trailingAnchor.constraint(equalTo: self.superview!.trailingAnchor)
viewConstraints = [bottomConstraint, leftConstraint, rightConstraint]
NSLayoutConstraint.activate(viewConstraints)
}
#IBAction func dismissButton(_ sender: Any) {
Logger.log(message: "Dismiss button was pressed", event: .d)
self.isHidden = true
}
And I add this UIView to my UIViewController by doing the following
var ConnectionLostAlert: ConnectionLostAlertUIView!
ConnectionLostAlert = ConnectionLostAlertUIView(frame: CGRect.zero)
self.view.addSubview(ConnectionLostAlert)
Well, I can use actual values for CGRect instead of zero but the problem is that after the constraints are added in my UIView, the button in my UIView stops responding. It works just fine if I actually set some CGRect coordinates and not use constraints in the UIView. Is there a better way to do what I am trying to achieve that will still allow me to handle the button event inside the UIView?
The problem is you are adding constraints from the inner view i.e view to the superview skipping the main view (ConnectionLostAlertUIView) that is actually being added to the super view.
What you should do instead is to add constraints between view and self first. Then when self(ConnectionLostAlertUIView) is added to the superview, add the constraints with super view then.
Do something like this:
#IBOutlet var view: UIView!
var viewConstraints: [NSLayoutConstraint] = []
override init(frame: CGRect) {
super.init(frame: frame)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
view.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
let leftConstraint = view.leadingAnchor.constraint(equalTo: self.leadingAnchor)
let rightConstraint = view.trailingAnchor.constraint(equalTo: self.trailingAnchor)
viewConstraints = [bottomConstraint, leftConstraint, rightConstraint]
NSLayoutConstraint.activate(viewConstraints)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
Bundle.main.loadNibNamed("ConnectionLostAlertView", owner: self, options: nil)
self.addSubview(self.view)
}
override func didMoveToSuperview() {
self.translatesAutoresizingMaskIntoConstraints = false
let bottomConstraint = self.bottomAnchor.constraint(equalTo: self.superview!.bottomAnchor)
let leftConstraint = self.leadingAnchor.constraint(equalTo: self.superview!.leadingAnchor)
let rightConstraint = self.trailingAnchor.constraint(equalTo: self.superview!.trailingAnchor)
viewConstraints = [bottomConstraint, leftConstraint, rightConstraint]
NSLayoutConstraint.activate(viewConstraints)
}
#IBAction func dismissButton(_ sender: Any) {
Logger.log(message: "Dismiss button was pressed", event: .d)
self.isHidden = true
}
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?