One UI element for all VC [closed] - swift

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 2 years ago.
Improve this question
How I can create Custom view element with animation like a separate class for all view controllers, so that not write this element each time for each VC.
For example:
I am want create bottom view with button and if I am press on button animation will be appeared, and I am can do different action for different VC.
How I can create this view with animation one time for all VC and implement different action inside VC?
I can only create it each time for each VC, all work done but it's terrible code and sometimes may be hard for maintenance.
This it example how I am create my bottom menu and this way looking good but how work.
import Foundation
import UIKit
class BottomMenu: UIViewController {
static let shared = BottomMenu()
var yPosBottomUIView: CGFloat = 90.0
var yPosCenterButton: CGFloat = 35
var yPosBackButton: CGFloat = 35
var yPosForwardButton: CGFloat = 35
var yPosContextMenu: CGFloat = 35
var yPosUpdateButton: CGFloat = 35
var backButtonEnable: Bool = false
var forwardButtonEnable: Bool = false
lazy var image: UIImageView = {
let image = UIImageView(frame: CGRect(x: 0, y: UIScreen.main.bounds.maxY - yPosBottomUIView, width: UIScreen.main.bounds.width, height: 90))
if (osTheme == .dark) {
DispatchQueue.main.async {
image.image = UIImage(named: "SurfaceBlack")
}
return image
} else {
DispatchQueue.main.async {
image.image = UIImage(named: "SurfaceLight")
}
return image
}
}()
lazy var centerButton: UIButton = {
let button = UIButton(frame: CGRect(x: (UIScreen.main.bounds.maxX / 2) - 25, y: (UIScreen.main.bounds.maxY - 50) - yPosCenterButton, width: 50, height: 50))
if (osTheme == .dark) {
button.backgroundColor = UIColor(red: 0.118, green: 0.118, blue: 0.15, alpha: 1)
} else {
button.backgroundColor = UIColor(red: 0.118, green: 0.118, blue: 0.15, alpha: 1)
}
button.layer.cornerRadius = 25
button.setImage(UIImage(named: "Menu"), for: .normal)
button.addTarget(self, action: #selector(centralButtonAction), for: .touchUpInside)
button.isEnabled = false
return button
}()
lazy var backButton: UIButton = {
let button = UIButton(frame: CGRect(x: (UIScreen.main.bounds.minX) + 25, y: (UIScreen.main.bounds.maxY - 15.5) - yPosBackButton, width: 36, height: 36))
button.backgroundColor = .clear
button.setImage(UIImage(named: "whiteBackArrow"), for: .normal)
button.addTarget(self, action: #selector(centralButtonAction), for: .touchUpInside)
button.isEnabled = backButtonEnable
return button
}()
lazy var forwardButton: UIButton = {
let button = UIButton(frame: CGRect(x: (UIScreen.main.bounds.minX) + 90, y: (UIScreen.main.bounds.maxY - 15.5) - yPosForwardButton, width: 36, height: 36))
button.backgroundColor = .clear
button.setImage(UIImage(named: "whiteBackArrow"), for: .normal)
button.imageView?.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
button.addTarget(self, action: #selector(centralButtonAction), for: .touchUpInside)
button.isEnabled = forwardButtonEnable
return button
}()
lazy var contextMenu: UIButton = {
let button = UIButton(frame: CGRect(x: (UIScreen.main.bounds.maxX) - 60, y: (UIScreen.main.bounds.maxY - 15.5) - yPosContextMenu, width: 36, height: 36))
button.backgroundColor = .clear
button.setImage(UIImage(named: "ContextMenu"), for: .normal)
button.addTarget(self, action: #selector(contextMenuButtonAction), for: .touchUpInside)
return button
}()
lazy var updateButton: UIButton = {
let button = UIButton(frame: CGRect(x: (UIScreen.main.bounds.maxX) - 125, y: (UIScreen.main.bounds.maxY - 15.5) - yPosUpdateButton, width: 36, height: 36))
button.backgroundColor = .clear
button.setImage(UIImage(named: "updateN"), for: .normal)
button.addTarget(self, action: #selector(centralButtonAction), for: .touchUpInside)
button.isEnabled = false
return button
}()
lazy var blur: UIVisualEffectView = {
let blurEffectView = UIVisualEffectView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height))
let blurEffect = UIBlurEffect(style: .dark)
blurEffectView.effect = blurEffect
blurEffectView.alpha = 0
return blurEffectView
}()
#objc func centralButtonAction(_ sender: UIButton?) {
print("Center Tapped")
}
#objc func contextMenuButtonAction(_ sender: UIButton?) {
print("Context Menu Tapped")
// IF I am past blur effect here and some action for diffent VC nothing happens but each time I'll saw printed text above)
}
}
This is image of menu which I am create but I am write code each time for each VC
Menu have ... when I press on it I am show the same animation for all VC and add some others view BUT When I am closed others sub view I am want implement different action depend of VC where menu did be opened.

Did you tried making one controller as base controller that can be inherited by all child controller and the common actions/activites to be part of that controller. Or maybe using a container view for child controller so that you can inherit properties of your base controller to your child controller.It depends on whatever suits your actual requirements

Related

Why does the selector, #objc, function add a subview, of my button image (as a UIImageView), in my UIButton?

I first declare the button
let balloon = UIButton()
A background image then gets added to the balloon
balloon.setBackgroundImage(UIImage(named:"balloon.jpg"), for: .normal)
An image view of the points get added to the balloon as a subview
subView = UIImageView(image: UIImage(named: "1") )
subView.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
subView.contentMode = UIView.ContentMode.scaleAspectFit
subView.center = CGPoint(x: balloon.frame.width/2, y: balloon.frame.height/2)
balloon.addSubview(subView)
I then use the addTarget function for the balloon
balloon.addTarget(self, action: #selector(pop), for: .touchUpInside)
After pop gets called (when the user taps on the balloon), the balloon now contains 2 subviews
- At index 0 of balloon.subviews, there is a UIImageView that is essentially the picture of the balloon with the same dimensions as the balloon button
- And the subs view that I added (aka the points)
here is how I found this problem in my addTarget function (pop):
#objc func pop(_ balloon: UIButton){
print("4.This is the balloon after calling pop \(balloon)")
print("5. This is the subview of the balloon after calling pop \(balloon.subviews)")
Ive added print statements in my function that verifies that the balloons are the same in both the pop func and my balloon creation func
I have already looked at the documentation for both UIButton and addTarget and neither of them have specified why the background image of the button gets created as a subview of the button when the selector func gets called
There shouldn't be that extra UIImageView in my UIButton since I never added that
What you're seeing has nothing to do with your selector / #objc func...
Many UIKit classes do a lot of "under-the-hood" work.
In the case of UIButton, subviews are only added as-needed.
For example, if this is all the code you execute:
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
The resulting button has Zero subviews.
If we do this:
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
The resulting button now has 1 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(img, for: .normal)
The resulting button now has 2 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(imgA, for: .normal)
balloon.addSubview(subview)
The resulting button now has 3 subviews.
let balloon = UIButton()
balloon.frame = CGRect(x: 100, y: 200, width: 240, height: 100)
view.addSubview(balloon)
balloon.setTitle("ABC", for: [])
balloon.setBackgroundImage(imgA, for: .normal)
balloon.addSubview(subview)
balloon.setImage(imgB, for: [])
And now we have 4 subviews.
Apple strongly discourages messing with the internals of UIButton. You might be better off creating a view subclass that contains a button and any additional subviews, rather than your current approach.
Worth Noting
the subviews may not be added until they are needed. So, if you set the title or image or background image in viewDidLoad(), those subviews will not be created until viewDidLayoutSubviews ... or even until the button is actually rendered.
the title label may be created even when you don't expect it to. For example, if the only thing you do to the button is constrain its position, it will get a title label. However, if you set the background image, the title label will be omitted.
Here is a quick example to demonstrate some of the resulting subview counts:
class BtnVC: UIViewController {
var buttons: [UIButton] = []
let stack = UIStackView()
let infoLabel: UILabel = {
let v = UILabel()
v.numberOfLines = 0
v.font = .monospacedSystemFont(ofSize: 14, weight: .regular)
return v
}()
var didLoadStr: String = ""
var didLayoutStr: String = ""
var didAppearStr: String = ""
override func viewDidLoad() {
super.viewDidLoad()
let largeFont = UIFont.systemFont(ofSize: 32)
let configuration = UIImage.SymbolConfiguration(font: largeFont) // <1>
guard let img2 = UIImage(systemName: "02.circle.fill", withConfiguration: configuration),
let img3 = UIImage(systemName: "03.circle.fill"),
let img4 = UIImage(systemName: "04.circle.fill")
else {
return
}
var b: UIButton!
b = UIButton()
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
b.setBackgroundImage(img3.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: [])
buttons.append(b)
b = UIButton()
b.setTitle("1", for: [])
b.setImage(img2, for: [])
b.setBackgroundImage(img3.withTintColor(.systemGreen, renderingMode: .alwaysOriginal), for: [])
let v = UIImageView(image: img4)
v.tintColor = .systemRed
v.frame = CGRect(x: 0, y: 0, width: 32, height: 32)
b.addSubview(v)
buttons.append(b)
stack.axis = .vertical
stack.spacing = 8
stack.distribution = .fillEqually
stack.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stack)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stack.topAnchor.constraint(equalTo: g.topAnchor, constant: 120.0),
stack.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
stack.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
stack.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
// add this first button *without* auto-layout
buttons[0].frame = CGRect(x: 80, y: 40, width: 200, height: 50)
view.addSubview(buttons[0])
// add the rest of the buttons to the stack view
buttons.forEach { v in
v.backgroundColor = .systemYellow
v.setTitleColor(.black, for: [])
if v != buttons.first {
stack.addArrangedSubview(v)
}
}
stack.addArrangedSubview(infoLabel)
var s = "didLoad : "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didLoadStr += s + "\n"
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// first button is not using auto-layout constraints, so
// let's size it to match the buttons in the stack view
// and posiiton it 8-pts above the stack view
var r = buttons[1].bounds
r.origin.y = stack.frame.origin.y - r.height - 8.0
r.origin.x = stack.frame.origin.x
buttons[0].frame = r
var s = "didLayout: "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didLayoutStr += s + "\n"
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
var s = "didAppear: "
buttons.forEach { v in
s += "\(v.subviews.count) / "
}
didAppearStr += s + "\n"
infoLabel.text = didLoadStr + didLayoutStr + didAppearStr
}
}
That code will create 5 buttons, with each successive button getting another subview - title, image, backgroundImage, addSubview - and the "Info Label" at the bottom will show the subviews count at each stage (as is often the case, didLayoutSubviews() is called more than once):

swift can't find textfield in scope even when its in the same viewController

I need to print value of txtField after clicking the button, txtField is on the viewcotroller but xcode returns an error cannot find 'txtField' in scope but they are in the same view
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
var txtField: UITextField = UITextField()
txtField.frame = CGRect(x: 50, y: 70, width: 200, height: 30)
txtField.backgroundColor = UIColor.gray
self.view.addSubview(txtField)
}
#objc func buttonAction(sender: UIButton!) {
print(txtField.text)
}
Actually, no, it's not in scope. Your var txtField is inside the viewDidLoad function. No other function's code can see inside this function, so the variable is not in scope from within another function such as your buttonAction.
In general the rule that things inside a scope can see only things that at a higher level of scope. If var txtField appeared outside both viewDidLoad and buttonAction (i.e. an instance property), then code inside both would be able to see it.
It's an easy move to make:
var txtField: UITextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
txtField.frame = CGRect(x: 50, y: 70, width: 200, height: 30)
txtField.backgroundColor = UIColor.gray
self.view.addSubview(txtField)
}
#objc func buttonAction(sender: UIButton!) {
print(txtField.text)
}
The error is that are in 2 different function, override or #objc doesn't change this simple thing. So move the txtField initialization outside them so can be readed or written by both.
var txtField: UITextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
button.backgroundColor = .green
button.setTitle("Test Button", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)
txtField.frame = CGRect(x: 50, y: 70, width: 200, height: 30)
txtField.backgroundColor = UIColor.gray
self.view.addSubview(txtField)
}
#objc func buttonAction(sender: UIButton!) {
print(txtField.text)
}
In your code txtField is declared in viewDidLoad on line
var txtField: UITextField = UITextField()
Therefore out of the scope of buttonAction.
To mitigate this place
txtField
out of scope as a instance variable

How to add UIImageView to navigation bar in swift?

I have this code that adds a rounded border around a UIImage using UIImageView and I've used UITapGestureRecognizer to let the user tap on the button:
var profilePicture = UIImageView()
func setupUserProfileButton() {
let defaultPicture = UIImage(named: "profilePictureSmall")
profilePicture = UIImageView(image: defaultPicture)
profilePicture.layer.cornerRadius = profilePicture.frame.width / 2
profilePicture.clipsToBounds = true
profilePicture.layer.borderColor = UIColor.black.cgColor
profilePicture.layer.borderWidth = 1
// Letting users click on the image
profilePicture.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(profilePictureTapped))
profilePicture.addGestureRecognizer(tapGesture)
}
How can I add this to the left side of a navigation bar? Is it possible? And I don't think the tap gesture is needed if I can add the ImageView to the navigation bar as a barButtonItem, so you can ignore that. I kinda found some similar questions but they were in objective C and none of what I tried worked.
Here is what I came up with based on an answer:
import UIKit
import Firebase
class CreateStoryPage: BaseAndExtensions {
let userProfileButton = UIButton(type: .custom)
override func viewDidLoad() {
super.viewDidLoad()
// Call all the elements
setupUserProfileButton()
}
// MARK:- Setups
// Setup the user profile button
func setupUserProfileButton() {
userProfileButton.setImage(#imageLiteral(resourceName: "profilePictureSmall.png"), for: .normal)
userProfileButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
userProfileButton.addTarget(self, action: #selector(profilePictureTapped), for: .touchUpInside)
let userProfileView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
userProfileView.layer.cornerRadius = 14
userProfileView.backgroundColor = .red
userProfileView.addSubview(userProfileButton)
let leftNavBarItem = UIBarButtonItem(customView: userProfileView)
self.navigationItem.setLeftBarButton(leftNavBarItem, animated: true)
}
// if user taps on profile picture
#objc func profilePictureTapped() {
let userProfilePage = UserProfilePage()
present(userProfilePage, animated: true, completion: nil)
}
}
Try this;
private func setupRightItem() {
let userProfileButton = UIButton(type: .custom)
userProfileButton.imageView?.contentMode = .scaleAspectFill
userProfileButton.clipsToBounds = true
userProfileButton.addTarget(self, action: #selector(profilePictureTapped), for: .touchUpInside)
userProfileButton.setImage(#imageLiteral(resourceName: "profilePictureSmall.png"), for: .normal)
userProfileButton.frame = CGRect(x: 0, y: 0, width: 30, height: 30)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: userProfileButton)
userProfileButton.widthAnchor.constraint(equalToConstant: 30).isActive = true
userProfileButton.heightAnchor.constraint(equalToConstant: 30).isActive = true
}
#objc private func goProfile() {
/// -> Action
}
let navBtn = UIButton(type: .custom)
navBtn.setImage("yourImage", for: .normal)
navBtn.frame = CGRect(x: 0, y: 0, width: 28, height: 28)
navBtn.addTarget(self, action: #selector(self.openProfile(_:)), for: .touchUpInside)
let view = UIView(frame: CGRect(x: 0, y: 0, width: 28, height: 28))
view.cornerRadius = 14
view.backgroundColor = Global.colorBlue
view.addSubview(navBtn)
let leftNavBarItem = UIBarButtonItem(customView: view)
self.navigationItem.setLeftBarButton(leftNavBarItem, animated: true)
#objc
func openProfile(_ sender: UIButton) {
}

Implementing the same function for different objects

Newbie coder and learning Swift. I want the function to be applicable for both UIButtons and couldn't figure out how to make it happen for second one.
private lazy var boostButton: UIButton = {
let button = UIButton(type: .custom)
button.frame = CGRect(x: 10, y: 10, width: 80, height: 80)
button.setImage(UIImage(named: "simsek.png"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .center
button.contentVerticalAlignment = .center
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter])
button.addTarget(self, action: #selector(touchUp), for: [.touchUpInside, .touchDragExit, .touchCancel])
return button
}()
private lazy var informationButton: UIButton = {
let button = UIButton(type: .custom)
button.frame = CGRect(x: 120, y: 10, width: 35, height: 35)
button.setImage(UIImage(named: "yenigozlukgri.png"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .center
button.contentVerticalAlignment = .center
button.layer.cornerRadius = 0.5 * button.bounds.size.width
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(touchDown), for: [.touchDown, .touchDragEnter])
button.addTarget(self, action: #selector(touchUp), for: [.touchUpInside, .touchDragExit, .touchCancel])
return button
}()
These are my buttons. I don't use storyboard but I believe that's not essential for the solution.
#objc func touchDown() {
animator.stopAnimation(true)
boostButton.backgroundColor = .red
//highlightedColor
}
#objc func touchUp() {
animator = UIViewPropertyAnimator(duration: 0.5, curve: .easeOut, animations: {
self.boostButton.backgroundColor = .gray
})
animator.startAnimation()
}
What I want to do is, when one of the buttons are clicked, it should perform the animation. If I add informationButton like the boostButton to my functions, both of them perform the animation even though one button is clicked. It should work for just the clicked one. How can I fix it to be functional for even more buttons ?
Use the parameter
#objc func touchDown(_ sender:UIButton) {
animator.stopAnimation(true)
sender.backgroundColor = .red
//highlightedColor
}
#objc func touchUp(_ sender:UIButton) {
animator = UIViewPropertyAnimator(duration: 0.5, curve: .easeOut, animations: {
sender.backgroundColor = .gray
})
animator.startAnimation()
}

How to have a button call a method programmatically in a class in swift?

I want the button to slide off the screen when it's hit, but that part of the code (buttonPressed) isn't working. When the method buttonPressed is inside the class, nothing happens when the button is pressed. Move the method outside of the brackets (to right before viewDidLoad) and the code executes. However, then it slides the entire view as opposed to just the tutorial window. I either need to find a way to make buttonPressed work as a method of the Tutorial class, or find a way to make it refer to the specific instance of "view" that's called in the class.
I'm new to coding and very new to methods, so any help is appreciated!
class Tutorial{
var label = UILabel()
var view = UIView()
var button = UIButton()
init (text: String){
view = UIView()
label = UILabel(frame: CGRect(x: 10, y: 10, width: 180, height: 90))
button = UIButton(frame: CGRect(x: 50, y: 110, width: 100, height: 30))
view.backgroundColor = .white
view.layer.cornerRadius = 15
view.translatesAutoresizingMaskIntoConstraints = false
label.textAlignment = .center
label.text = text
label.numberOfLines = 10
label.backgroundColor = .white
label.textColor = UIColor(red:0.12, green:0.15, blue:0.23, alpha:1.0)
button.backgroundColor = UIColor(red:0.23, green:0.72, blue:0.44, alpha:1.0)
button.setTitleColor(.white, for: .normal)
button.setTitle("Got it!", for: .normal)
button.layer.cornerRadius = 15
view.addSubview(label)
view.addSubview(button)
button.addTarget(self, action: #selector(buttonPressed), for: UIControlEvents.touchUpInside)
}
func setConstraints(height: CGFloat){
view.centerXAnchor.constraint(equalTo: view.superview!.centerXAnchor).isActive = true
view.topAnchor.constraint(equalTo: view.superview!.topAnchor, constant: UIScreen.main.bounds.height-300).isActive = true
view.widthAnchor.constraint(equalToConstant: 200).isActive = true
view.heightAnchor.constraint(equalToConstant: height).isActive = true
UIView.animate(withDuration: 0.5, delay: 0.2, options: [], animations: {
self.view.center.x -= UIScreen.main.bounds.width
})
}
#objc func buttonPressed(){
print("Pressed")
UIView.animate(withDuration: 0.5, delay: 0.0, options: [], animations: {
self.view.center.x -= UIScreen.main.bounds.width
},
completion: { (finished: Bool) in
self.view.isHidden = true
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
let tutorial1 = Tutorial(text: "Click and hold to see the anatomy overlay")
self.view.addSubview(tutorial1.view)
tutorial1.setConstraints(height: 150)
Try using Interface Builder Constants. Here is the Apple Documentation. https://developer.apple.com/documentation/appkit/constants/interface_builder_constants?language=objc
make an IBOutlet and an IBAction for your button.