Trying to update local json file - ios

To start off, I want to say that I am new to using JSON, so if this code needs some cleanup also, let me know. So I am trying to update json file. I have created an empty jsonfile named data.json in my project, inside a folder I named Data. The file looks like this:
{
"fullname": "Testuser2",
"answered": 0,
"correct": 0
}
This is my JSONData.swift:
import Foundation
struct UserData: Codable {
let fullname: String
let answered: Int
let correct: Int
}
struct GlobalVariable{
static var fullname = String()
static var answered = Int()
static var correct = Int()
}
These function to read the json file:
private func readLocalFile(forName name: String) -> Data? {
do {
if let bundlePath = Bundle.main.path(forResource: name,
ofType: "json"),
let jsonData = try String(contentsOfFile: bundlePath).data(using: .utf8) {
return jsonData
}
} catch {
print(error)
}
return nil
}
private func parse(jsonData: Data) {
do {
let decodedData = try JSONDecoder().decode(UserData.self,
from: jsonData)
GlobalVariable.fullname = decodedData.fullname
GlobalVariable.answered = decodedData.answered
GlobalVariable.correct = decodedData.correct
} catch {
print("decode error")
}
}
And this is how I use it:
func getJSON() {
if let localData = self.readLocalFile(forName: "data") {
self.parse(jsonData: localData)
print(GlobalVariable.fullname)
print(GlobalVariable.answered)
print(GlobalVariable.correct)
}
}
So the getJSON successfully shows me the correct values.
Now after the GlobalVariable.answered and GlobalVariable.correct has been incrementet, I want to save it.
This is the code I am trying to use to save it:
func saveJSON {
print("---------")
print(GlobalVariable.fullname)
print(GlobalVariable.answered)
print(GlobalVariable.correct)
print("---------")
let dataToUpdate = [UserData(fullname: GlobalVariable.fullname, answered: GlobalVariable.answered, correct: GlobalVariable.correct)]
do {
let jsonData = try JSONEncoder().encode(dataToUpdate)
let jsonString = String(data: jsonData, encoding: .utf8)!
print(jsonString)
if let documentDirectory = FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent("data.json")
do {
try jsonString.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
} catch {
// Handle error
}
}
} catch { print(error) }
}
The three print codes inside saveJSON shows the correct value, but someone nothing gets saved to the json file. The json file stays untouched.. How come?
I've also tried removing the .json from let pathWithFilename = documentDirectory.appendingPathComponent("data.json"), but still not working..

You are starting with your data.json in your bundle.
You read that, change the values, and then write it to Documents folder.
So, when you want to read the file, you need to first see if it exists in the Documents folder. If so, read it from there. If it doesn't exist (e.g. first time the app is run), then read it from the bundle.
Edit
The general idea is:
try to load the json file from the app's Documents folder
if that fails
create a "blank" user data object and show the Name Input view
if we successfully loaded the json file
use that data
after every change (name was entered, user answered a question), update the user data object and save (overwrite) the json file in the Documents folder
No need to have a .json file in your bundle, because you'll create a blank user data object if there is no .json file in the Documents folder.
Here is a complete example you can run and inspect:
struct UserData: Codable {
var fullname: String
var answered: Int
var correct: Int
}
class FileTestVC: UIViewController {
var userData: UserData!
let nameTextField = UITextField()
let nameLabel = UILabel()
let questionLabel = UILabel()
let resultNameLabel = UILabel()
let resultLabel = UILabel()
let userNameView = UIView()
let questionView = UIView()
let restartView = UIView()
let numQuestions: Int = 10
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
setupNameView()
setupQuestionView()
setupRestartView()
[userNameView, questionView, restartView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
v.layer.cornerRadius = 16
v.layer.borderWidth = 2
v.isHidden = true
view.addSubview(v)
}
userNameView.layer.borderColor = UIColor.systemBlue.cgColor
questionView.layer.borderColor = UIColor.systemRed.cgColor
restartView.layer.borderColor = UIColor.systemGreen.cgColor
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
userNameView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
userNameView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
questionView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
questionView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
restartView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
restartView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
userData = UserData(fullname: "", answered: 0, correct: 0)
if let jData = readLocalFile(forName: "data.json") {
do {
userData = try JSONDecoder().decode(UserData.self, from: jData)
} catch {
print(error)
}
}
if userData.fullname.isEmpty {
userNameView.isHidden = false
} else {
if userData.answered < numQuestions {
questionView.isHidden = false
nameLabel.text = userData.fullname
showNextQuestion()
} else {
showResult()
}
}
}
func setupNameView() {
// add a text field and "Save" button to userNameView
nameTextField.borderStyle = .roundedRect
let saveBtn = UIButton()
saveBtn.setTitle("Save", for: [])
saveBtn.setTitleColor(.white, for: .normal)
saveBtn.setTitleColor(.lightGray, for: .highlighted)
saveBtn.backgroundColor = .systemBlue
saveBtn.layer.cornerRadius = 8
[nameTextField, saveBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
userNameView.addSubview(v)
}
let g = userNameView
NSLayoutConstraint.activate([
nameTextField.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
nameTextField.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
nameTextField.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
nameTextField.widthAnchor.constraint(equalToConstant: 280.0),
saveBtn.topAnchor.constraint(equalTo: nameTextField.bottomAnchor, constant: 20.0),
saveBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
saveBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
saveBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
saveBtn.addTarget(self, action: #selector(saveNameTapped(_:)), for: .touchUpInside)
}
func setupQuestionView() {
// add labels + Right and Wrong buttons to questionView
nameLabel.textAlignment = .center
nameLabel.backgroundColor = .yellow
nameLabel.text = "Name"
questionLabel.textAlignment = .center
questionLabel.backgroundColor = .green
questionLabel.numberOfLines = 0
questionLabel.text = "Question"
let rightBtn = UIButton()
rightBtn.setTitle("Right", for: [])
rightBtn.setTitleColor(.white, for: .normal)
rightBtn.setTitleColor(.lightGray, for: .highlighted)
rightBtn.backgroundColor = .systemGreen
rightBtn.layer.cornerRadius = 8
let wrongBtn = UIButton()
wrongBtn.setTitle("Wrong", for: [])
wrongBtn.setTitleColor(.white, for: .normal)
wrongBtn.setTitleColor(.lightGray, for: .highlighted)
wrongBtn.backgroundColor = .systemRed
wrongBtn.layer.cornerRadius = 8
[nameLabel, questionLabel, rightBtn, wrongBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
questionView.addSubview(v)
}
let g = questionView
NSLayoutConstraint.activate([
nameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
nameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
nameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
questionLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 20.0),
questionLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
questionLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
questionLabel.widthAnchor.constraint(equalToConstant: 280.0),
rightBtn.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20.0),
rightBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
wrongBtn.topAnchor.constraint(equalTo: questionLabel.bottomAnchor, constant: 20.0),
wrongBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
wrongBtn.leadingAnchor.constraint(equalTo: rightBtn.trailingAnchor, constant: 20.0),
wrongBtn.widthAnchor.constraint(equalTo: rightBtn.widthAnchor),
wrongBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
rightBtn.addTarget(self, action: #selector(rightAnswerTapped(_:)), for: .touchUpInside)
wrongBtn.addTarget(self, action: #selector(wrongAnswerTapped(_:)), for: .touchUpInside)
}
func setupRestartView() {
// add labels + button to restartView
resultNameLabel.textAlignment = .center
resultNameLabel.backgroundColor = .yellow
resultNameLabel.text = "name"
resultLabel.textAlignment = .center
resultLabel.backgroundColor = .green
resultLabel.numberOfLines = 0
resultLabel.text = "You answered"
let restartBtn = UIButton()
restartBtn.setTitle("Restart", for: [])
restartBtn.setTitleColor(.white, for: .normal)
restartBtn.setTitleColor(.lightGray, for: .highlighted)
restartBtn.backgroundColor = .systemBlue
restartBtn.layer.cornerRadius = 8
[resultNameLabel, resultLabel, restartBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
restartView.addSubview(v)
}
let g = restartView
NSLayoutConstraint.activate([
resultNameLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
resultNameLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
resultNameLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
resultLabel.topAnchor.constraint(equalTo: resultNameLabel.bottomAnchor, constant: 20.0),
resultLabel.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
resultLabel.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
resultLabel.widthAnchor.constraint(equalToConstant: 280.0),
restartBtn.topAnchor.constraint(equalTo: resultLabel.bottomAnchor, constant: 20.0),
restartBtn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
restartBtn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
restartBtn.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
])
restartBtn.addTarget(self, action: #selector(restartTapped(_:)), for: .touchUpInside)
}
#objc func saveNameTapped(_ sender: Any?) {
guard let s = nameTextField.text,
!s.isEmpty
else { return }
view.endEditing(true)
userNameView.isHidden = true
questionView.isHidden = false
userData.fullname = s
nameLabel.text = userData.fullname
saveJSON(userData)
questionLabel.text = "Question \(userData.answered + 1) of \(numQuestions)\nAnswer Right or Wrong"
}
#objc func restartTapped(_ sender: Any?) {
// "empty" user data object
userData = UserData(fullname: "", answered: 0, correct: 0)
// save (overwrite) existing data
saveJSON(userData)
restartView.isHidden = true
nameTextField.text = ""
userNameView.isHidden = false
}
#objc func rightAnswerTapped(_ sender: Any?) {
userData.correct += 1
showNextQuestion()
}
#objc func wrongAnswerTapped(_ sender: Any?) {
showNextQuestion()
}
func showNextQuestion() {
userData.answered += 1
saveJSON(userData)
if userData.answered < numQuestions {
questionLabel.text = "Question \(userData.answered + 1) of \(numQuestions)\nAnswer Right or Wrong"
} else {
questionView.isHidden = true
showResult()
}
}
func showResult() {
restartView.isHidden = false
resultNameLabel.text = userData.fullname
resultLabel.text = "You answered \(userData.correct) Right!"
}
private func readLocalFile(forName name: String) -> Data? {
if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent(name)
do {
let jd = try String(contentsOf: pathWithFilename)
return jd.data(using: .utf8)
} catch {
print(error)
}
return nil
}
return nil
}
func saveJSON(_ d: UserData) {
do {
let jsonData = try JSONEncoder().encode(d)
let jsonString = String(data: jsonData, encoding: .utf8)!
if let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let pathWithFilename = documentDirectory.appendingPathComponent("data.json")
do {
try jsonString.write(to: pathWithFilename,
atomically: true,
encoding: .utf8)
} catch {
print(error)
}
}
} catch {
print(error)
}
}
}

Related

setting adjustsImageWhenHighlighted with UIButtonConfiguration

Since adjustsImageWhenHighlighted is deprecated in iOS 15.0, what's the way to set it with UIButtonConfiguration? If I set it directly, I get a message saying "You may customize to replicate this behavior via a configurationUpdateHandler", but what exactly should I be doing to set this in configurationUpdateHandler? Thanks
This is the basics...
Assuming you have "normal" and "highlighted" images:
// let's use new UIButton.Configuration
newButton.configuration = .plain()
newButton.configurationUpdateHandler = { button in
var config = button.configuration
config?.image = button.isHighlighted ? imgHighlighted : imgNormal
button.configuration = config
}
Here's a runnable example, that uses UIGraphicsImageRenderer to create a "darkened" version of the button image "on-the-fly":
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
guard let imgNormal = UIImage(named: "myButtonImage")
else {
print("Could not load images!")
return
}
// generate a "darkened" version of the image
let rndr = UIGraphicsImageRenderer(size: imgNormal.size)
let imgHighlighted = rndr.image { (ctx) in
imgNormal.draw(at: .zero)
UIColor.black.withAlphaComponent(0.5).setFill()
ctx.cgContext.fill(CGRect(origin: .zero, size: imgNormal.size))
}
let oldButton = UIButton()
let newButton = UIButton()
// "standard" button
oldButton.setImage(imgNormal, for: [])
// default is .adjustsImageWhenHighlighted = true
// let's use new UIButton.Configuration
newButton.configuration = .plain()
newButton.configurationUpdateHandler = { button in
var config = button.configuration
config?.image = button.isHighlighted ? imgHighlighted : imgNormal
button.configuration = config
}
[oldButton, newButton].forEach { b in
b.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(b)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
oldButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
oldButton.widthAnchor.constraint(equalToConstant: 80.0),
oldButton.heightAnchor.constraint(equalTo: oldButton.widthAnchor),
oldButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
newButton.topAnchor.constraint(equalTo: oldButton.bottomAnchor, constant: 20.0),
newButton.widthAnchor.constraint(equalTo: oldButton.widthAnchor),
newButton.heightAnchor.constraint(equalTo: oldButton.widthAnchor),
newButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
])
}
}
When you run this, the two buttons should look and function the same.

Shadow disappears then reappears on UITableView updates

I am using tableView.beginUpdates() and tableView.endUpdates() to expand/contract a cell that has shadow. When the table updates, it removes all shadows from cells and then puts them back as shown here
I have tried using willDisplayCell and also tried changing the shadow to its own view, shadow on contentView and shadow on cell, none worked.
How do i keep the shadow?
extension UIView {
func addTutShadow(shadowOpacity: Float? = nil) {
self.layer.shadowColor = UIColor.black.cgColor
self.layer.shadowOffset = CGSize(width: 0, height: 3)
self.layer.shadowRadius = 12 * kHeightFactor
self.layer.shadowOpacity = shadowOpacity ?? 0.12
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TopicListCard
cell.topic = rankedTopics[indexPath.section]
cell.backgroundColor = .clear
cell.backgroundView = UIView()
cell.selectedBackgroundView = UIView()
cell.delegate = self
cell.addTutShadow()
cell.setup()
return cell
}
var selectedIndex = -1
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if selectedIndex != indexPath.section {
selectedIndex = indexPath.section
tableView.beginUpdates()
tableView.endUpdates()
} else {
selectedIndex = -1
tableview.deselectRow(at: indexPath, animated: true)
tableView.beginUpdates()
tableView.endUpdates()
}
}
It's tough to say exactly, without seeing you full cell setup...
I whipped up a quick example of one way to do this...
Looks like this:
Data Structs
struct Topic {
var title: String = ""
var status: String = ""
var icon: String = ""
}
struct TopicCellStruct {
var topic: Topic = Topic()
var expanded: Bool = false
}
Simple gradient view - for the background
class MyGradView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
gLayer.colors = [
UIColor(red: 0.71, green: 0.88, blue: 1.0, alpha: 1).cgColor,
UIColor(red: 0.95, green: 0.95, blue: 1.0, alpha: 1).cgColor
]
gLayer.locations = [0, 1]
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
}
Example controller class - with some sample generated data
class ShadowTestVC: UIViewController {
var myData: [TopicCellStruct] = []
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// some sample data
let p1 = "You have correctly answered "
let p2 = "% of the questions you attempted in this topic."
var pct: Int = 5
let titles: [String] = [
"Basic Algebra",
"Isolating a Variable",
"Absolute Value",
"Solving Linear Equations",
"Solving Radical Equations",
]
let syms: [String] = [
"function",
"multiply.circle",
"ruler.fill",
"arrow.up.arrow.down",
"x.squareroot",
]
for (title, sym) in zip(titles, syms) {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: title, status: s, icon: sym)
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
for i in 6...9 {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: "Title \(i)", status: s, icon: "\(i).circle.fill")
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
// a label above the table view
let topUI = UILabel()
topUI.font = .systemFont(ofSize: 24, weight: .regular)
topUI.numberOfLines = 0
topUI.text = "This is some text to represent the UI elements above the table view."
// a gradient view for the background
let gradientBKGView = MyGradView()
[gradientBKGView, topUI, tableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradientBKGView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
gradientBKGView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
gradientBKGView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
gradientBKGView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
topUI.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0),
topUI.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
topUI.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0),
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0),
])
tableView.backgroundColor = .clear
tableView.register(ShadowedCell.self, forCellReuseIdentifier: ShadowedCell.ident)
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
}
}
extension ShadowTestVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: ShadowedCell.ident, for: indexPath) as! ShadowedCell
c.fillData(myData[indexPath.row])
c.selectionStyle = .none
return c
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let c = tableView.cellForRow(at: indexPath) as? ShadowedCell else { return }
myData[indexPath.row].expanded.toggle()
c.isExpanded = myData[indexPath.row].expanded
UIView.animate(withDuration: 0.3, animations: {
tableView.performBatchUpdates(nil, completion: nil)
})
}
}
Example cell class - I made some guesses at your layout, and I set the shadow darker than yours (.shadowOpacity = 0.75) to make it a bit more visible.
class ShadowedCell: UITableViewCell {
public static let ident: String = "sc"
public var isExpanded: Bool = false {
didSet {
expandedConstraint.isActive = isExpanded
ivVerticalExpandedConstraint.isActive = isExpanded
ivHorizontalExpandedConstraint.isActive = isExpanded
}
}
private let titleLabel = UILabel()
private let statusLabel = UILabel()
private let theImageView = UIImageView()
private let getStartedBtn = UIButton()
private let containerView = UIView()
private let shadowView = UIView()
private var collapsedConstraint: NSLayoutConstraint!
private var expandedConstraint: NSLayoutConstraint!
private var ivVerticalCollapsedConstraint: NSLayoutConstraint!
private var ivVerticalExpandedConstraint: NSLayoutConstraint!
private var ivHorizontalCollapsedConstraint: NSLayoutConstraint!
private var ivHorizontalExpandedConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
clipsToBounds = true
contentView.clipsToBounds = true
containerView.clipsToBounds = true
[shadowView, containerView, theImageView, titleLabel, statusLabel, getStartedBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
[titleLabel, statusLabel, getStartedBtn].forEach { v in
v.setContentHuggingPriority(.required, for: .vertical)
v.setContentCompressionResistancePriority(.required, for: .vertical)
}
[titleLabel, statusLabel, theImageView, getStartedBtn].forEach { v in
containerView.addSubview(v)
}
contentView.addSubview(shadowView)
contentView.addSubview(containerView)
contentView.backgroundColor = .clear
self.backgroundColor = .clear
shadowView.backgroundColor = .white
titleLabel.numberOfLines = 0
titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular)
statusLabel.numberOfLines = 0
statusLabel.font = .systemFont(ofSize: 18.0, weight: .regular)
getStartedBtn.setTitle("Get Started", for: [])
getStartedBtn.setTitleColor(.white, for: .normal)
getStartedBtn.setTitleColor(.lightGray, for: .highlighted)
getStartedBtn.backgroundColor = .black
getStartedBtn.layer.cornerRadius = 12
let g = contentView.layoutMarginsGuide
collapsedConstraint = titleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0)
expandedConstraint = getStartedBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16.0)
ivVerticalCollapsedConstraint = theImageView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5.0)
ivVerticalExpandedConstraint = theImageView.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor, constant: 0.0)
ivHorizontalCollapsedConstraint = theImageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0)
ivHorizontalExpandedConstraint = theImageView.leadingAnchor.constraint(equalTo: statusLabel.trailingAnchor, constant: 16.0)
collapsedConstraint.priority = .required - 2
expandedConstraint.priority = .required - 1
ivVerticalCollapsedConstraint.priority = .required - 2
ivVerticalExpandedConstraint.priority = .required - 1
ivHorizontalCollapsedConstraint.priority = .required - 2
ivHorizontalExpandedConstraint.priority = .required - 1
NSLayoutConstraint.activate([
shadowView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
shadowView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
shadowView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
shadowView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
titleLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
titleLabel.heightAnchor.constraint(equalToConstant: 60.0),
statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0),
statusLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
statusLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
theImageView.widthAnchor.constraint(equalToConstant: 50.0),
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor),
getStartedBtn.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32.0),
getStartedBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
getStartedBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0),
collapsedConstraint, ivVerticalCollapsedConstraint, ivHorizontalCollapsedConstraint,
])
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
shadowView.layer.shadowOpacity = 0.75
shadowView.layer.cornerRadius = 16.0
}
public func fillData(_ t: TopicCellStruct) {
titleLabel.text = t.topic.title
statusLabel.text = t.topic.status
if let img = UIImage(systemName: t.topic.icon) {
theImageView.image = img
}
isExpanded = t.expanded
}
}
Give that a try... it should avoid the shadow issue you were seeing. Then compare my approach to yours.
As a side note: I'm sure you already noticed in your development... while the design is very nice with transparent cells and table view, the expand/collapse process looks a bit "quirky" as the cells that were "out of view" (below the bottom of the table view frame) don't really "slide up" along with the other cells.
Edit -- a couple of very minor changes to improve the "glitchy" lower-cells-animation...
All classes*
struct Topic {
var title: String = ""
var status: String = ""
var icon: String = ""
}
struct TopicCellStruct {
var topic: Topic = Topic()
var expanded: Bool = false
}
class ShadowedCell: UITableViewCell {
public static let ident: String = "sc"
private let titleLabel = UILabel()
private let statusLabel = UILabel()
private let theImageView = UIImageView()
private let getStartedBtn = UIButton()
private let containerView = UIView()
private let shadowView = UIView()
private var collapsedConstraint: NSLayoutConstraint!
private var expandedConstraint: NSLayoutConstraint!
private var ivVerticalCollapsedConstraint: NSLayoutConstraint!
private var ivVerticalExpandedConstraint: NSLayoutConstraint!
private var ivHorizontalCollapsedConstraint: NSLayoutConstraint!
private var ivHorizontalExpandedConstraint: NSLayoutConstraint!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
clipsToBounds = true
contentView.clipsToBounds = true
containerView.clipsToBounds = true
[shadowView, containerView, theImageView, titleLabel, statusLabel, getStartedBtn].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
}
[titleLabel, statusLabel, getStartedBtn].forEach { v in
v.setContentHuggingPriority(.required, for: .vertical)
v.setContentCompressionResistancePriority(.required, for: .vertical)
}
[titleLabel, statusLabel, theImageView, getStartedBtn].forEach { v in
containerView.addSubview(v)
}
contentView.addSubview(shadowView)
contentView.addSubview(containerView)
contentView.backgroundColor = .clear
self.backgroundColor = .clear
shadowView.backgroundColor = .white
titleLabel.numberOfLines = 0
titleLabel.font = .systemFont(ofSize: 20.0, weight: .regular)
statusLabel.numberOfLines = 0
statusLabel.font = .systemFont(ofSize: 18.0, weight: .regular)
getStartedBtn.setTitle("Get Started", for: [])
getStartedBtn.setTitleColor(.white, for: .normal)
getStartedBtn.setTitleColor(.lightGray, for: .highlighted)
getStartedBtn.backgroundColor = .black
getStartedBtn.layer.cornerRadius = 12
let g = contentView.layoutMarginsGuide
collapsedConstraint = titleLabel.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0)
expandedConstraint = getStartedBtn.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16.0)
ivVerticalCollapsedConstraint = theImageView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 5.0)
ivVerticalExpandedConstraint = theImageView.centerYAnchor.constraint(equalTo: statusLabel.centerYAnchor, constant: 0.0)
ivHorizontalCollapsedConstraint = theImageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0)
ivHorizontalExpandedConstraint = theImageView.leadingAnchor.constraint(equalTo: statusLabel.trailingAnchor, constant: 16.0)
collapsedConstraint.priority = .required - 2
expandedConstraint.priority = .required - 1
ivVerticalCollapsedConstraint.priority = .required - 2
ivVerticalExpandedConstraint.priority = .required - 1
ivHorizontalCollapsedConstraint.priority = .required - 2
ivHorizontalExpandedConstraint.priority = .required - 1
NSLayoutConstraint.activate([
shadowView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
shadowView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
shadowView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
shadowView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
titleLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
titleLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
titleLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
titleLabel.heightAnchor.constraint(equalToConstant: 60.0),
statusLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12.0),
statusLabel.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
statusLabel.widthAnchor.constraint(equalTo: containerView.widthAnchor, multiplier: 0.6),
theImageView.widthAnchor.constraint(equalToConstant: 50.0),
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor),
getStartedBtn.topAnchor.constraint(equalTo: statusLabel.bottomAnchor, constant: 32.0),
getStartedBtn.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16.0),
getStartedBtn.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16.0),
collapsedConstraint, ivVerticalCollapsedConstraint, ivHorizontalCollapsedConstraint,
])
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize(width: 0.0, height: 3.0)
shadowView.layer.shadowOpacity = 0.75
shadowView.layer.cornerRadius = 16.0
}
public func fillData(_ t: TopicCellStruct) {
titleLabel.text = t.topic.title
statusLabel.text = t.topic.status
if let img = UIImage(systemName: t.topic.icon) {
theImageView.image = img
}
expandedConstraint.isActive = t.expanded
ivVerticalExpandedConstraint.isActive = t.expanded
ivHorizontalExpandedConstraint.isActive = t.expanded
}
public func expand(_ isExpanded: Bool) {
expandedConstraint.isActive = isExpanded
ivVerticalExpandedConstraint.isActive = isExpanded
ivHorizontalExpandedConstraint.isActive = isExpanded
UIView.animate(withDuration: 0.3, animations: {
self.layoutIfNeeded()
})
}
}
class ShadowTestVC: UIViewController {
var myData: [TopicCellStruct] = []
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
// some sample data
let p1 = "You have correctly answered "
let p2 = "% of the questions you attempted in this topic."
var pct: Int = 5
let titles: [String] = [
"Basic Algebra",
"Isolating a Variable",
"Absolute Value",
"Solving Linear Equations",
"Solving Radical Equations",
]
let syms: [String] = [
"function",
"multiply.circle",
"ruler.fill",
"arrow.up.arrow.down",
"x.squareroot",
]
for (title, sym) in zip(titles, syms) {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: title, status: s, icon: sym)
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
for i in 6...19 {
pct += 4
let s: String = p1 + "\(pct)" + p2
let t = Topic(title: "Title \(i)", status: s, icon: "\(i).circle.fill")
let tcs = TopicCellStruct(topic: t, expanded: false)
myData.append(tcs)
}
// a label above the table view
let topUI = UILabel()
topUI.font = .systemFont(ofSize: 24, weight: .regular)
topUI.numberOfLines = 0
topUI.text = "This is some text to represent the UI elements above the table view."
// a gradient view for the background
let gradientBKGView = MyGradView()
[gradientBKGView, topUI, tableView].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(v)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
gradientBKGView.topAnchor.constraint(equalTo: g.topAnchor, constant: 0.0),
gradientBKGView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 0.0),
gradientBKGView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: 0.0),
gradientBKGView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: 0.0),
topUI.topAnchor.constraint(equalTo: g.topAnchor, constant: 32.0),
topUI.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 32.0),
topUI.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -48.0),
tableView.topAnchor.constraint(equalTo: g.topAnchor, constant: 160.0),
tableView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 16.0),
tableView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -16.0),
tableView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -16.0),
])
tableView.backgroundColor = .clear
tableView.register(ShadowedCell.self, forCellReuseIdentifier: ShadowedCell.ident)
tableView.dataSource = self
tableView.delegate = self
tableView.separatorStyle = .none
}
}
extension ShadowTestVC: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCell(withIdentifier: ShadowedCell.ident, for: indexPath) as! ShadowedCell
c.fillData(myData[indexPath.row])
c.selectionStyle = .none
return c
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let c = tableView.cellForRow(at: indexPath) as? ShadowedCell else { return }
myData[indexPath.row].expanded.toggle()
c.expand(myData[indexPath.row].expanded)
tableView.beginUpdates()
tableView.endUpdates()
}
}
class MyGradView: UIView {
override class var layerClass: AnyClass {
return CAGradientLayer.self
}
private var gLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
func commonInit() {
gLayer.colors = [
UIColor(red: 0.71, green: 0.88, blue: 1.0, alpha: 1).cgColor,
UIColor(red: 0.95, green: 0.95, blue: 1.0, alpha: 1).cgColor
]
gLayer.locations = [0, 1]
gLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
}

Get offset when Scrollview is animated

I have a scorll view with animating
UIView.animate(withDuration: Double(totalMidiTime) / 1000, delay: 0, options: .curveLinear) {
self.scrollView.contentOffset.x = self.scrollView.contentSize.width - self.leftMargin
} completion: { (_) in }
I want to get current postion offset when it's animating
You can get the offset by checking the bounds.origin.x value of the scroll view's presentation layer:
guard let pl = scrollView.layer.presentation() else {
return
}
print("Content Offset X:", pl.bounds.origin.x)
Here's a complete example...
We create a scroll view with 30 labels. On viewDidAppear we start a 20-second horizontal animation to the last label. Each time we tap the button, the "status label" will update with the current bounds.origin.x of the scroll view's presentation layer (which matches the contentOffset.x):
class ViewController: UIViewController {
let scrollView: UIScrollView = {
let v = UIScrollView()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .systemGreen
return v
}()
let testButton: UIButton = {
let v = UIButton()
v.translatesAutoresizingMaskIntoConstraints = false
v.backgroundColor = .blue
v.setTitle("Get Offset", for: [])
v.setTitleColor(.lightGray, for: .highlighted)
return v
}()
let statusLabel: UILabel = {
let v = UILabel()
v.translatesAutoresizingMaskIntoConstraints = false
v.numberOfLines = 0
v.textAlignment = .center
v.text = "Content Offset X\n0.00"
return v
}()
override func viewDidLoad() {
super.viewDidLoad()
let stack = UIStackView()
stack.distribution = .fillEqually
stack.spacing = 8
stack.translatesAutoresizingMaskIntoConstraints = false
for i in 1...30 {
let v = UILabel()
v.backgroundColor = .yellow
v.text = "Label \(i)"
v.textAlignment = .center
stack.addArrangedSubview(v)
}
scrollView.addSubview(stack)
view.addSubview(scrollView)
view.addSubview(testButton)
view.addSubview(statusLabel)
let g = view.safeAreaLayoutGuide
let cg = scrollView.contentLayoutGuide
let fg = scrollView.frameLayoutGuide
NSLayoutConstraint.activate([
scrollView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
scrollView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
scrollView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
scrollView.heightAnchor.constraint(equalToConstant: 60.0),
stack.topAnchor.constraint(equalTo: cg.topAnchor, constant: 8.0),
stack.leadingAnchor.constraint(equalTo: cg.leadingAnchor, constant: 8.0),
stack.trailingAnchor.constraint(equalTo: cg.trailingAnchor, constant: -8.0),
stack.bottomAnchor.constraint(equalTo: cg.bottomAnchor),
stack.heightAnchor.constraint(equalTo: fg.heightAnchor, constant: -16.0),
testButton.topAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 20.0),
testButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
testButton.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.6),
statusLabel.topAnchor.constraint(equalTo: testButton.bottomAnchor, constant: 20.0),
statusLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
statusLabel.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
])
testButton.addTarget(self, action: #selector(gotTap(_:)), for: .touchUpInside)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIView.animate(withDuration: 20.0, delay: 0, options: .curveLinear) {
self.scrollView.contentOffset.x = self.scrollView.contentSize.width - self.scrollView.frame.width
} completion: { (_) in }
}
#objc func gotTap(_ sender: UIButton) -> Void {
guard let pl = scrollView.layer.presentation() else {
return
}
let x = String(format: "%0.2f", pl.bounds.origin.x)
statusLabel.text = "Content Offset X\n\(x)"
}
}

How to add a button a UIView()

So I have
let verticalViewOne = UIView()
I want to add the following button to this view
/* Station */
DRN1Button.tintColor = .systemPink
DRN1Button.frame.size = CGSize(width: 200.0, height: 200.0)
DRN1Button.setImage(UIImage(named: "DRN1.jpg"), for: UIControl.State(rawValue: UIControl.State.RawValue(play)))
//playButton.imageView?.contentMode = .scaleAspectFit
DRN1Button.contentVerticalAlignment = .fill
DRN1Button.contentHorizontalAlignment = .fill
DRN1Button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
DRN1Button.addTarget(self, action: #selector(playdrn1), for: .touchUpInside)
DRN1Button.translatesAutoresizingMaskIntoConstraints = false
// self.view.addSubview(playButton)
verticalStackView.addSubview(DRN1Button)
My question is how do I go about this?
as this code
verticalStackView.addSubview(DRN1Button)
brings an error that crashes the app.
NOTE: the button is override func viewDidLoad() { while the verticalStackView is set in
func setupHierachy(){
view.addSubview(containerStackView)
view.addSubview(verticalStackView)
verticalStackView.addArrangedSubview(verticalViewOne)
//verticalStackView.addArrangedSubview(DRN1Button)
containerStackView.addArrangedSubview(verticalStackView)
}
Error am getting is
Thread 1: Exception: "Unable to activate constraint with anchors <NSLayoutYAxisAnchor:0x281f52640 \"UIButton:0x10540ba90.centerY\"> and <NSLayoutYAxisAnchor:0x281f52680 \"UIView:0x10530ea40.centerY\"> because they have no common ancestor. Does the constraint or its anchors reference items in different view hierarchies? That's illegal."
full code.
//
// ViewController.swift
// 1life.radio
//
// Created by Russell Harrower on 19/3/20.
// Copyright © 2020 Russell Harrower. All rights reserved.
//
import UIKit
import AVKit
import Network
import Kingfisher
struct Nowplayng: Decodable{
let data: [Data]
}
struct Data: Decodable{
let track: [Trackinfo]
}
struct Trackinfo: Decodable {
let title: String
let artist: String
let imageurl: String
}
class ViewController: UIViewController {
let uuid = UIDevice.current.identifierForVendor?.uuidString
let playButton = UIButton(frame: CGRect(x: 0, y:0, width: 100, height: 100))
let DRN1Button = UIButton(frame: CGRect(x: 0, y:0, width: 100, height: 48))
var play = 0;
var npinfo = "https://api.drn1.com.au/station/1lifeRadio/playing"
var currentstation = "";
var timer = Timer()
/* V VIEWS */
let containerStackView = UIStackView()
let verticalStackView = UIStackView()
let verticalViewOne = UIView()
let verticalViewTwo = UIView()
let verticalViewThree = UIView()
/* END V VIEWS */
var isPlaying = false {
didSet {
self.playButton.setImage(UIImage(systemName: self.isPlaying ? "play.circle" : "pause.circle"), for: UIControl.State(rawValue: UIControl.State.RawValue(play)))
//self.isPlaying ? MusicPlayer.shared.startBackgroundMusic() : MusicPlayer.shared.stopBackgroundMusic()
self.isPlaying ? MusicPlayer.shared.stopBackgroundMusic() : MusicPlayer.shared.startBackgroundMusic(url: currentstation)
}
}
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupHierachy()
setlayout()
currentstation = "http://stream.radiomedia.com.au:8015/stream?uuid=\(self.uuid ?? "")"
// Do any additional setup after loading the view.
MusicPlayer.shared.startBackgroundMusic(url: "http://stream.radiomedia.com.au:8015/stream?uuid=\(self.uuid ?? "")")
playButton.tintColor = .systemPink
playButton.frame.size = CGSize(width: 200.0, height: 200.0)
playButton.setImage(UIImage(systemName: "pause.circle"), for: UIControl.State(rawValue: UIControl.State.RawValue(play)))
//playButton.imageView?.contentMode = .scaleAspectFit
playButton.contentVerticalAlignment = .fill
playButton.contentHorizontalAlignment = .fill
playButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
playButton.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
playButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(playButton)
//verticalViewOne.addSubview(DRN1Button)
NSLayoutConstraint.activate([
playButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
playButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
playButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -208),
//playButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12),
//playButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -12),
playButton.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.2),
playButton.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.2),
/* DRN1Button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
DRN1Button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
DRN1Button.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0),
//playButton.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12),
//playButton.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -12),
DRN1Button.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.2),
DRN1Button.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.2)*/
])
self.artist.textAlignment = .center
self.song.textAlignment = .center
nowplaying()
scheduledTimerWithTimeInterval()
}
func setupViews(){
verticalStackView.axis = .vertical
verticalStackView.translatesAutoresizingMaskIntoConstraints = true
verticalStackView.distribution = .fillEqually
verticalViewOne.backgroundColor = .red
containerStackView.backgroundColor = .green
containerStackView.axis = .vertical
containerStackView.spacing = 15
containerStackView.distribution = .fillEqually
}
func setupHierachy(){
view.addSubview(containerStackView)
view.addSubview(verticalStackView)
verticalStackView.addArrangedSubview(verticalViewOne)
//verticalStackView.addArrangedSubview(DRN1Button)
containerStackView.addArrangedSubview(verticalStackView)
}
func setlayout(){
containerStackView.translatesAutoresizingMaskIntoConstraints = false
containerStackView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
containerStackView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2).isActive = true
containerStackView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
containerStackView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
}
#objc func appDidLaunch() {
#if targetEnvironment(simulator)
/*
if you don’t set up remote controls, the AVPlayer from
AVPlayerController will immediately create default remote controls
otherwise your simulator will not show the controls.
*/
self.isPlaying = false
self.playButton.setImage(UIImage(systemName: "pause.circle"), for: UIControl.State(rawValue: UIControl.State.RawValue(play)))
MusicPlayer.shared.startBackgroundMusic(url: "http://stream.radiomedia.com.au:8015/stream?uuid=\(uuid ?? "")")
#else
// MusicPlayer.shared.setupRemoteTransportControls()
// more custom controls set up
#endif
}
#objc func playdrn1(sender:UIButton!){
currentstation = "http://stream.radiomedia.com.au:8003/stream?uuid=\(uuid ?? "")"
MusicPlayer.shared.startBackgroundMusic(url: "http://stream.radiomedia.com.au:8003/stream?uuid=\(uuid ?? "")")
npinfo = "https://api.drn1.com.au/station/DRN1/playing"
}
#objc func buttonAction(sender: UIButton!) {
print(self.isPlaying)
self.isPlaying = !self.isPlaying
}
#IBOutlet var imageurl: UIImageView!
#IBOutlet var artist: UILabel!
#IBOutlet var song: UILabel!
func scheduledTimerWithTimeInterval(){
// Scheduling timer to Call the function "updateCounting" with the interval of 1 seconds
timer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(self.nowplaying), userInfo: nil, repeats: true)
}
#objc func nowplaying(){
let jsonURLString = npinfo
guard let feedurl = URL(string: jsonURLString) else { return }
URLSession.shared.dataTask(with: feedurl) { (data,response,err)
in
guard let data = data else { return }
do{
let nowplaying = try JSONDecoder().decode(Nowplayng.self, from: data)
//print(nowplaying.data.first?.track.first?.artist as Any)
nowplaying.data.forEach {_ in
DispatchQueue.main.async {
self.artist.text = nowplaying.data.first?.track.first?.artist
self.song.text = nowplaying.data.first?.track.first?.title
}
if var strUrl = nowplaying.data.first?.track.first?.imageurl {
strUrl = strUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
DispatchQueue.main.sync {
let processor = DownsamplingImageProcessor(size: self.imageurl.bounds.size)
|> RoundCornerImageProcessor(cornerRadius: 20)
self.imageurl.kf.indicatorType = .activity
self.imageurl.kf.setImage(
with: URL(string: strUrl),
placeholder: UIImage(named: "placeholderImage"),
options: [
.processor(processor),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(1)),
.cacheSerializer(FormatIndicatedCacheSerializer.png),
.cacheOriginalImage
])
{
result in
switch result {
case .success(let value):
print("Task done for: \(value.source.url?.absoluteString ?? "")")
case .failure(let error):
print("Job failed: \(error.localizedDescription)")
}
}
}
// self.imageurl.kf.setImage(with: URL(string: strUrl), placeholder: nil)
//MusicPlayer.shared.nowplaying(artist: $0.track.artist, song: $0.track.title, cover:strUrl)
// MusicPlayer.shared.getArtBoard(artist: $0.track.artist, song: $0.track.title, cover:strUrl)
}
}
}catch let jsonErr{
print("error json ", jsonErr)
}
}.resume()
}
}
First off, the line:
verticalStackView.addSubview(DRN1Button)
crashes because you are attempting to add a subview to stackView, you're supposed to do addArrangedSubview.
For the crash:
Thread 1: Exception: "Unable to activate constraint with anchors
<NSLayoutYAxisAnchor:0x281f52640 "UIButton:0x10540ba90.centerY"> and
<NSLayoutYAxisAnchor:0x281f52680 "UIView:0x10530ea40.centerY">
because they have no common ancestor. Does the constraint or its
anchors reference items in different view hierarchies? That's
illegal."
You have it already.
DRN1Button.centerYAnchor.constraint(equalTo: view.centerYAnchor)
Your DRN1Button has no common ancestor with your self.view. This can mean that you haven't added your DRN1Button to a view, or most probably the line above, can't be done. While your button is in a stackview but you are setting its centerYAnchor equal to your self.view.

CIHeightFieldFromMask output image is the same as input

I've been using various CIFilters to achieve a certain effect, but when narrowing down my problem I've found that CIHeightFieldFromMask yields no result; meaning the mask image looks exactly the same after I apply the filter. I'm using the same exact black and white text image used in the Apple Docs.
ciImage = ciImage
.applyingFilter("CIHeightFieldFromMask", parameters: [kCIInputImageKey: ciImage,
kCIInputRadiusKey: ShadedHeightMaskInputRadius])
ShadedHeightMaskInputRadius is a value that I can change ranging from 0 to 100 with a slider, so I've also tried all kinds of input radii with no difference. How can I achieve the same exact result shown in the documentation?
Not sure why your attempt is failing...
This might work for you:
extension UIImage {
func applyHeightMap(withRadius: Int) -> UIImage {
guard let thisCI = CIImage(image: self) else { return self }
let newCI = thisCI
.applyingFilter("CIHeightFieldFromMask",
parameters: [kCIInputRadiusKey: withRadius])
return UIImage(ciImage: newCI)
}
}
Usage would be:
let newImg = self.maskImg.applyHeightMap(withRadius: self.radius)
Here is some sample code to try it out. It has a 0 to 50 range slider for the radius. This is (as you probably know) very slow on a simulator, so this updates the image only when you release the slider:
class CITestViewController: UIViewController {
let theSlider: UISlider = {
let v = UISlider()
return v
}()
let theLabel: UILabel = {
let v = UILabel()
v.text = " "
return v
}()
let theImageView: UIImageView = {
let v = UIImageView()
v.contentMode = .scaleAspectFit
v.backgroundColor = .blue
return v
}()
let theSpinner: UIActivityIndicatorView = {
let v = UIActivityIndicatorView()
return v
}()
var maskImg: UIImage!
var radius: Int = 10
override func viewDidLoad() {
super.viewDidLoad()
guard let mImg = UIImage(named: "ci_heightmask") else {
fatalError("could not load ci_heightmask image")
}
maskImg = mImg
[theSlider, theLabel, theImageView, theSpinner].forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
theSlider.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
theSlider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
theSlider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
theLabel.topAnchor.constraint(equalTo: theSlider.bottomAnchor, constant: 20.0),
theLabel.centerXAnchor.constraint(equalTo: theSlider.centerXAnchor, constant: 0.0),
theImageView.topAnchor.constraint(equalTo: theLabel.bottomAnchor, constant: 20.0),
theImageView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
theImageView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
theImageView.heightAnchor.constraint(equalTo: theImageView.widthAnchor),
theSpinner.centerXAnchor.constraint(equalTo: theSlider.centerXAnchor),
theSpinner.centerYAnchor.constraint(equalTo: theSlider.centerYAnchor),
])
theSlider.minimumValue = 0
theSlider.maximumValue = 1
theSlider.value = Float(radius) / 50.0
theSlider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
theSlider.addTarget(self, action: #selector(sliderReleased(_:)), for: .touchUpInside)
theLabel.text = "Radius: \(radius)"
updateImage()
}
#objc func sliderChanged(_ sender: Any) {
if let s = sender as? UISlider {
let v = Int((s.value * 50).rounded())
if radius != v {
radius = v
theLabel.text = "Radius: \(radius)"
}
}
}
#objc func sliderReleased(_ sender: Any) {
updateImage()
}
func updateImage() -> Void {
theSlider.isHidden = true
theSpinner.startAnimating()
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
let newImg = self.maskImg.applyHeightMap(withRadius: self.radius)
DispatchQueue.main.async {
self.theImageView.image = newImg
self.theSpinner.stopAnimating()
self.theSlider.isHidden = false
}
}
}
}
ci_heightmask.png
Results:

Resources