right now I have a collectionView in which I can add cells by tapping the "addCell".
My goal is that if the user taps the "addCell" a view should appear where the user can type in a title for the cell and select an image like so:
Any idea on how I could realize that?
class ContentCell: UICollectionViewCell {
let testImage: UIImageView = {
let v = UIImageView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .cyan
return v
}()
let testLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.text = "Test Label"
v.font = UIFont(name: "Avenir Next-Bold", size: 18)
v.textColor = .darkGray
v.textAlignment = .center
return v
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
contentView.addSubview(testLabel)
contentView.addSubview(testImage)
NSLayoutConstraint.activate([
testImage.topAnchor.constraint(equalTo: contentView.topAnchor),
testImage.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
testImage.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
testImage.heightAnchor.constraint(equalToConstant:150),
testLabel.topAnchor.constraint(equalTo: testImage.bottomAnchor,constant: 1),
testLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
testLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
testLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
])
}
}
so in didSelectItemAtIndexPath you present the view controller to ask for the image image and title, and then in that view controller you declare a delegate or completion handler that when the user presses "done" or "save" in the view controller where they selected the image and title, the delegate or completion handler is then called and then tells the original view controller with the cells to add the item you just create and reload the collectionView. this is pretty standard stuff and this same pattern you'll need to learn how to use since most apps use this this same pattern 100s of times per app. i can show you from Objective-C but won't waste my time showing this is Swift. lmk if you need more help. good luck
oh yes, and for future reference, it's best to stick with delegation since using completion handlers gets very tricky when you encounter cases with nested completions and still need to maintain a weakified then strongified reference to the controller. after two degress of weakifying and strogifying "self" the system will start to randomly deallocate your controllers. Stick with delegation.
Related
I'm working on making a custom UIButton in Swift and have a question for initializing the UIButton with type custom.
This is the image of the current custom button in my project, and when the user taps a button, the image icon, whose the original color is .whilte, grays out. However, I want to keed the image color to white even when the user taps the button and the button state changes. I think I should initialize the button with type custom, but I get the message like, Must call a designated initializer of the superclass 'UIButton', when I try initializing with init(type: UIButton.ButtonType), so could someone point me to the right direction, please?
Here is the code, for the custom button class.
import UIKit
class MyCapsuleButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(backgroundColor: UIColor, title: String, textColor: UIColor) {
super.init(frame: .zero)
// super.init(type: .custom) -> tried to initialize with type, but didn't work
self.backgroundColor = backgroundColor
self.setTitle(title, for: .normal)
self.setTitleColor(textColor, for: .normal)
configure()
}
func configure() {
translatesAutoresizingMaskIntoConstraints = false
titleLabel?.font = UIFont.customNormal()
}
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = self.frame.height / 2
}
}
and I call as
lazy var deletionButton: MyCapsuleButton = {
let button = MyCapsuleButton(backgroundColor: .Red(), title: "DELETE", textColor: .white)
button.setImage(Images.delete, for: .normal)
return button
}()
I read the documentation and it says You specify the type of a button at creation time using the init(type:) method, I thought I need to call super.init(type: .custom) in the custom initializer, but I get a "Must call..." error on the storyboard. Also, I dont't use a storyboard in this project, and I want to know how can I call type custom with some custom init parameters, like backgroundColor, title, textColor.
Add this part later...
So, it seems when I make a subclass of UIButton, the type is gonna be custom by default. (I printed out the type and figured out.)
So is setting button.setImage(Images.delete, for: .normal) makes the trash icon gray?
When the Highlighted Adjusts Image (adjustsImageWhenHighlighted) option is enabled, button images get darker when it’s in the highlighted state.
So, You should turn off that attribute like below.
button.adjustsImageWhenHighlighted = false
OR, You can turn off in the storyboard.
Note below screen-shot.
https://i.stack.imgur.com/N42m4.png
I've created a subclass to manage my Theme but is not showing neither on device or simulator.
Here my Header.swift:
import Foundation
import UIKit
class Header: UILabel {
override var textColor: UIColor! {
// White Color
get { return ThemeManager.currentTheme.palette.primary }
set {}
}
override var font: UIFont! {
get { return ThemeManager.currentTheme.textStyle.headerText }
set {}
}
}
Here the implementation: (inside the viewcontroller)
var titleLabel: Header = Header()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black
// Background Image over the view
setupBackground()
setupStartButton()
setupTitleLabel()
print(titleLabel.frame)
}
// MARK: - Header
private func setupTitleLabel() {
titleLabel.text = "0.0m"
// titleLabel.font = ThemeManager.currentTheme.textStyle.headerText
// titleLabel.textColor = ThemeManager.currentTheme.palette.primary
view.addSubview(titleLabel)
view.bringSubviewToFront(titleLabel)
setupTitleLabelAutolayout()
}
private func setupTitleLabelAutolayout() {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
titleLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
}
But if I use UILabel instead of Header it works perfectly as expected.
I've also tried to implement init?(coder aDecoder: NSCoder) and init(frame: CGRect) but nothing changed.
If I set a frame on init then shows the text, but not styled and ignoring my constraints.
Surely I'm missing something, but what?
To avoid usefulness answers, here some infos:
The UILabel textColor is white
The background is black and has an image over it.
I've tried to remove the image and all the stuff around except for the label and nothing changed.
That's a poor reason to use subclassing. It doesn't allow you to mix-and-match when appropriate.
Better would be to make an extension:
extension UILabel {
func withHeaderStyle() -> UILabel {
self.textColor = ThemeManager.currentTheme.palette.primary
self.font = ThemeManager.currentTheme.textStyle.headerText
return self
}
}
Then at point of use:
var titleLabel = UILabel().withHeaderStyle()
You can make several of these "withXStyle" methods and at the point of use you can chain them together. That's something you can't do with inheritance.
In general you should only use inherence when you want to change behavior. It's ill suited for changing data.
I've fixed that by editing the Header to this:
import Foundation
import UIKit
class Header: UILabel {
override init(frame: CGRect) {
super.init(frame: frame)
setupStyle()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupStyle()
}
private func setupStyle() {
self.textColor = ThemeManager.currentTheme.palette.primary
self.font = ThemeManager.currentTheme.textStyle.headerText
}
}
Basically if I understood right, when I set the getter in the label it doesn't (if you think about, it's quite obvious) anything.
I still think that there are better solutions, but this works fine for me so, I'll keep that.
Now you may ask: "Why did you overwritten the getter instead of doing this?"
It's the right question, and the right answer is that I read it in a swift article on medium, so I tought it was right.
PS: I've also tried with didSet but it obviously loop through it self and crash.
I have a header of a UICollectionView. This header contains a UILabel. I'm updating the text value of the label by calling a function updateClue through a delegate.
class LetterTileHeader: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var clueLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "HELLO"
label.textColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
func setupViews() {
backgroundColor = GlobalConstants.colorDarkBlue
addSubview(clueLabel)
clueLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
clueLabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
clueLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
clueLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
}
func updateClue(clue: String) {
clueLabel.text = clue
debugLabel()
}
func debugLabel() {
print(clueLabel.text)
}
The function debugLabel is printing with the changed text coming from updateClue. But the label on the screen does not update. What am I missing? I've tried using a DispatchQueue.main.async block with changing the label text within that but no still no change.
UpdateClue function is triggered from another class via a Protocol Delegation.
extension PuzzleViewController: WordTileDelegate {
func relaySelectedWordId(wordId: String!) {
selectableTiles.updateTileHeaderClue(wordId: wordId)
}
}
Thanks.
UPDATE:
I've figured out what's going on. This subview is created each time I select a new cell. Looking into why that's happening. Will update soon.
The issue I'm having is due to this cell class being initialized multiple times. I believe the label I'm seeing is not the current one being updated, but the previous label that was instantiated. I have a new issue though which I may open a question on if I can't figure it out.
You have two UILabels and/or two LetterTileHeaders. The one you are updating is not the one that is being displayed on the screen.
One way to prove this is to put a breakpoint on the line after let label = UILabel(). It is very likely that the breakpoint will be hit more than once.
Another way would be to look at the screen layout in the debugger and find the memory location of the UILabel that is getting displayed, then put a breakpoint in the updateClue method and compare that label's memory location to the one that is being displayed on the screen.
Follow code seems to be wrong, this mean each time when you use clueLble it will give you fresh UILable instance.
var clueLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "HELLO"
label.textColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
Try doing this :
just replace above code with
var clueLabel = UILabel()
Add following func:
override func awakeFromNib() {
super.awakeFromNib()
label.font = UIFont.systemFont(ofSize: 16)
label.text = "HELLO"
label.textColor = UIColor.white
label.translatesAutoresizingMaskIntoConstraints = false
backgroundColor = GlobalConstants.colorDarkBlue
addSubview(clueLabel)
clueLabel.topAnchor.constraint(equalTo: self.topAnchor).isActive = true
clueLabel.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
clueLabel.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
clueLabel.leftAnchor.constraint(equalTo: self.leftAnchor).isActive = true
}
remove setupViews()
I've started to build my first open source project I would like to share to a world. It's a simple Audio Player view which will work with AudioProvidable protocol and then will play an audio data after it's received.
For now I have an IBDesignable class inherits from UIView which builds itself by defining constraints for its subviews like this:
override init(frame: CGRect) {
super.init(frame: frame)
#if !TARGET_INTERFACE
translatesAutoresizingMaskIntoConstraints = false
#endif
prepareView()
updateUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
prepareView()
updateUI()
}
func prepareView() {
//Necessary in order to set our own constraints
//btnPlay is already doing the same inside buttonWithImage(_:)
spinner.translatesAutoresizingMaskIntoConstraints = false
sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false
//Add necessary subviews to start setting up layout
addSubview(sliderAudioProgress)
addSubview(lblAudioDuration)
addSubview(lblAudioProgress)
addSubview(spinner)
addSubview(btnPlay)
addSubview(lblTrackNumber)
addSubview(btnNextTrack)
addSubview(btnPreviousTrack)
//Height of play & next/previous track buttons defines AudioPlayerView height
let viewHeight:CGFloat = 48 * 2
//Setup UI layout using constraints
NSLayoutConstraint.activate([
heightAnchor.constraint(equalToConstant: viewHeight),
//Play button constraints
btnPlay.topAnchor.constraint(equalTo: topAnchor),
btnPlay.leadingAnchor.constraint(equalTo: leadingAnchor),
//Spinner constraints
spinner.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
spinner.centerXAnchor.constraint(equalTo: btnPlay.centerXAnchor),
//Progress label constraints
lblAudioProgress.leadingAnchor.constraint(equalTo: btnPlay.trailingAnchor),
lblAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
//Duration label constraints
lblAudioDuration.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -spacing),
lblAudioDuration.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
//Audio Progress Slider constraints
sliderAudioProgress.leadingAnchor.constraint(equalTo: lblAudioProgress.trailingAnchor, constant: spacing),
sliderAudioProgress.trailingAnchor.constraint(equalTo: lblAudioDuration.leadingAnchor, constant: -spacing),
sliderAudioProgress.centerYAnchor.constraint(equalTo: btnPlay.centerYAnchor),
lblTrackNumber.centerXAnchor.constraint(equalTo: centerXAnchor),
btnPreviousTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor),
btnPreviousTrack.trailingAnchor.constraint(equalTo: lblTrackNumber.leadingAnchor, constant: -spacing),
btnNextTrack.topAnchor.constraint(equalTo: btnPlay.bottomAnchor),
btnNextTrack.leadingAnchor.constraint(equalTo: lblTrackNumber.trailingAnchor, constant: spacing),
lblTrackNumber.centerYAnchor.constraint(equalTo: btnNextTrack.centerYAnchor)
])
//Spinner setup:
spinner.hidesWhenStopped = true
//btnPlay setup:
btnPlay.addTarget(self, action: #selector(AudioPlayerView.playButtonPressed), for: .touchUpInside)
//Slider setup
sliderAudioProgress.thumbTintColor = UIColor(keyFromAppColorPalette: "secondary_color")
sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderValueChanged), for: .valueChanged)
sliderAudioProgress.addTarget(self, action: #selector(AudioPlayerView.durationSliderReleased), for: .touchUpInside)
sliderAudioProgress.isEnabled = false
sliderAudioProgress.isContinuous = true
sliderAudioProgress.semanticContentAttribute = .playback
//View's UI effects to make it look better
layer.cornerRadius = 6
layer.masksToBounds = true
layer.borderColor = UIColor.black.cgColor
layer.borderWidth = 2
semanticContentAttribute = .playback
spinner.startAnimating()
btnPlay.isHidden = true
}
As a result that is what we have when audio is receiving:
After the audio received:
In case when there's only one audio to play the bottom part, switches between tracks is redundant. Where do I have to put a checking code for audio tracks count to redesign my UI? I don't want to call prepareView() in didSet {} of audio source array, because I don't want existing elements be added twice.
Regards.
You could add a bool flag to see if the code is running for the first time, or, what I find easier and maybe a little more logical...
func prepareView() {
//Necessary in order to set our own constraints
//btnPlay is already doing the same inside buttonWithImage(_:)
spinner.translatesAutoresizingMaskIntoConstraints = false
sliderAudioProgress.translatesAutoresizingMaskIntoConstraints = false
// if our subviews have not yet been added
if sliderAudioProgress.superview == nil {
//Add necessary subviews to start setting up layout
addSubview(sliderAudioProgress)
addSubview(lblAudioDuration)
... etc
}
// continue here with any "every time" setup tasks
}
I have three sections, the header and a container where a tab bar can facilitate transition of pages all within that container. I want to build a UI like the figure below.
My approach is to create two container views one for the header and the other for tabbar and selected pages. The tabbar and selected pages are UICollectionViewCell. So I will be putting UICollectionViewCell in a UIView container.
in my BusinessHomeViewController.swift file
class BusinessHomeViewController: UIViewController {
let businessPagesContainer: BusinessPages = {
let bp = BusinessPages()
return bp
}()
func setupbusinessPagesContainerView(){
businessPagesContainer.heightAnchor.constraint(equalToConstant: 80).isActive = true
businessPagesContainer.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
businessPagesContainer.topAnchor.constraint(equalTo: businessDescriptionView.bottomAnchor, constant: 90).isActive = true
view.addSubview(menuBar)
menuBar.topAnchor.constraint(equalTo: pageToggleContainer.topAnchor, constant: 2).isActive = true
menuBar.centerXAnchor.constraint(equalTo: pageToggleContainer.centerXAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(businessPagesContainer)
setupbusinessPagesContainerView()
}
}
In my BusinessPages.swift file
import UIKit
class BusinessPages: UICollectionViewCell {
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I run the app, it crashes and gave this error "terminating with uncaught exception of type NSException". Is my approach wrong and how can I fix this?
If you're running into this problem make sure that you go to Main.storyboard, RIGHT click on the yellow box icon (view controller) at the top of the phone outline and DELETE the outlet(s) with yellow flags.