Swift3 How to set properties created in an extension - uiviewcontroller

I have a function in my UIViewControllers to setup a UINavigationBar which is repeated in many functions. I want to create the navigation bar in an extension but I want to set the title text and a cart label in each function. How can I do this? I think the answer is to use protocols but I'm not sure how.
Here is my extension
extension UIViewController {
func shoppingBagButtonTouched(button: UIButton) {
-----
}
func closeView() {
dismiss(animated: true, completion: nil)
}
func setupNavigationHeader(showCart: Bool? = true) {
let navigationBar: UINavigationBar = {
let navBar = UINavigationBar(frame: CGRect(0, 0, self.view.frame.size.width, Constants.HEADER_HEIGHT))
return navBar
}()
let navigationItem = UINavigationItem()
self.automaticallyAdjustsScrollViewInsets = false
UINavigationBar.appearance().barTintColor = .red
UINavigationBar.appearance().tintColor = .white
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.layer.masksToBounds = true
label.minimumScaleFactor = 10/UIFont.labelFontSize
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = 1
label.text = "not set"
label.textColor = .white
return label
}()
let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
let menuBtn = UIBarButtonItem(image: UIImage(named: "closeNav"), style: .plain, target: self, action: #selector(self.closeView))
navigationItem.leftBarButtonItems = [fixedSpace, menuBtn]
navigationBar.items = [navigationItem]
if let showCart = showCart {
let cartCountLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: -0, width: 20, height: 20))
label.textAlignment = .center
label.layer.cornerRadius = label.bounds.size.height / 2
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.layer.masksToBounds = true
label.textColor = .white
label.minimumScaleFactor = 10/UIFont.labelFontSize
label.adjustsFontSizeToFitWidth = true
return label
}()
let shoppingBagButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 22, height: 22))
button.setBackgroundImage(UIImage(named: "shopping_bag"), for: .normal)
return button
}()
let rightBarButtonItem = UIBarButtonItem(customView: shoppingBagButton)
navigationItem.setRightBarButtonItems([rightBarButtonItem], animated: true)
shoppingBagButton.addTarget(self, action: #selector(shoppingBagButtonTouched(button:)), for: .touchUpInside)
shoppingBagButton.addSubview(cartCountLabel)
cartCountLabel.anchorCenterXToSuperview()
cartCountLabel.anchorCenterYToSuperview(constant: 2)
}
navigationBar.addSubview(titleLabel)
view.addSubview(navigationBar)
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
titleLabel.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -10).isActive = true
titleLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
titleLabel.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 90).isActive = true
}
}
In each controller I have code like this to set the title label and cart label but it doesn't work when I create the nav bar in an extension.
var titleLabelText: String = "title not set"
var cartCount: String? {
didSet {
cartCountLabel.text = cartCount
}
}
func getCartCount() {
ServerUtility.getCartCountApi { (cartCount) in
if let count = cartCount as Int? {
if count > 0 {
self.cartCount = "\(count)"
}
} else {
self.cartCount = "0"
}
}
}

You are probably best off creating a base class extending UIViewController something like this:
class BaseViewController: UIViewController {
let cartCountLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: -0, width: 20, height: 20))
label.textAlignment = .center
label.layer.cornerRadius = label.bounds.size.height / 2
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.layer.masksToBounds = true
label.textColor = .white
label.minimumScaleFactor = 10/UIFont.labelFontSize
label.adjustsFontSizeToFitWidth = true
return label
}()
func shoppingBagButtonTouched(button: UIButton) {
-----
}
func closeView() {
dismiss(animated: true, completion: nil)
}
func setupNavigationHeader(showCart: Bool? = true) {
let navigationBar: UINavigationBar = {
let navBar = UINavigationBar(frame: CGRect(0, 0, self.view.frame.size.width, Constants.HEADER_HEIGHT))
return navBar
}()
let navigationItem = UINavigationItem()
self.automaticallyAdjustsScrollViewInsets = false
UINavigationBar.appearance().barTintColor = .red
UINavigationBar.appearance().tintColor = .white
let titleLabel: UILabel = {
let label = UILabel()
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .clear
label.layer.masksToBounds = true
label.minimumScaleFactor = 10/UIFont.labelFontSize
label.adjustsFontSizeToFitWidth = true
label.numberOfLines = 1
label.text = "not set"
label.textColor = .white
return label
}()
let fixedSpace = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: nil, action: nil)
let menuBtn = UIBarButtonItem(image: UIImage(named: "closeNav"), style: .plain, target: self, action: #selector(self.closeView))
navigationItem.leftBarButtonItems = [fixedSpace, menuBtn]
navigationBar.items = [navigationItem]
if let showCart = showCart {
let shoppingBagButton: UIButton = {
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 22, height: 22))
button.setBackgroundImage(UIImage(named: "shopping_bag"), for: .normal)
return button
}()
let rightBarButtonItem = UIBarButtonItem(customView: shoppingBagButton)
navigationItem.setRightBarButtonItems([rightBarButtonItem], animated: true)
shoppingBagButton.addTarget(self, action: #selector(shoppingBagButtonTouched(button:)), for: .touchUpInside)
shoppingBagButton.addSubview(cartCountLabel)
cartCountLabel.anchorCenterXToSuperview()
cartCountLabel.anchorCenterYToSuperview(constant: 2)
}
navigationBar.addSubview(titleLabel)
view.addSubview(navigationBar)
titleLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
titleLabel.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -10).isActive = true
titleLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
titleLabel.widthAnchor.constraint(equalToConstant: UIScreen.main.bounds.width - 90).isActive = true
}
}
Then you can derive all your view controller instances from the base class like this:
class MyViewController:BaseViewController {
var titleLabelText: String = "title not set"
var cartCount: String? {
didSet {
cartCountLabel.text = cartCount
}
}
func getCartCount() {
ServerUtility.getCartCountApi { (cartCount) in
if let count = cartCount as Int? {
if count > 0 {
self.cartCount = "\(count)"
}
} else {
self.cartCount = "0"
}
}
}
}
I have not verified the above code by running it and so it might need a bit of tweaking :)

Related

SwiftUI: Attributed string from HTML that wraps

I have two strings that can contain HTML strings.
HTML string can only contain simple text formatting like bold, italic etc.
I need to combine these in an attributed string and show in a list.
Every list item may have a leading or a trailing item or both at the same time.
I am using a UILabel wrapper to have a wrapping text view. I am using this wrapper to show this attributed string. This wrapper only works correctly if I set preferredMaxLayoutWidth value. So I need to give a width to wrapper. when the text view is the only item on horizontal axis it is working fine because I am giving it a constant width:
In case the list with attributed strings, the text view randomly having extra padding at the top and bottom:
This is how I am generating attributed string from HTML string:
func htmlToAttributedString(
fontName: String = AppFonts.hebooRegular.rawValue,
fontSize: CGFloat = 12.0,
color: UIColor? = nil,
lineHeightMultiple: CGFloat = 1
) -> NSAttributedString? {
guard let data = data(using: .unicode, allowLossyConversion: true) else { return nil }
do {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineHeightMultiple = lineHeightMultiple
paragraphStyle.alignment = .left
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.lineSpacing = 0
let attributedString = try NSMutableAttributedString(
data: data,
options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
],
documentAttributes: nil
)
attributedString.addAttribute(
NSAttributedString.Key.paragraphStyle,
value: paragraphStyle,
range: NSMakeRange(0, attributedString.length)
)
if let font = UIFont(name: fontName, size: fontSize) {
attributedString.addAttribute(
NSAttributedString.Key.font,
value: font,
range: NSMakeRange(0, attributedString.length)
)
}
if let color {
attributedString.addAttribute(
NSAttributedString.Key.foregroundColor,
value: color,
range: NSMakeRange(0, attributedString.length)
)
}
return attributedString
} catch {
return nil
}
}
And this is my UILabel wrapper:
public struct AttributedLabel: UIViewRepresentable {
public func makeUIView(context: Context) -> UIViewType {
let label = UIViewType()
label.numberOfLines = 0
label.lineBreakMode = .byWordWrapping
let result = items.map { item in
let result = item.text.htmlToAttributedString(
fontName: item.fontName,
fontSize: item.fontSize,
color: item.color,
lineHeightMultiple: lineHeightMultiple
)
guard let result else {
return NSAttributedString(string: "")
}
return result
}.reduce(NSMutableAttributedString(string: "")) { result, text in
if result.length > 0 {
result.append(NSAttributedString(string: " "))
}
result.append(text)
return result
}
let height = result.boundingRect(
with: .init(width: width, height: .infinity),
options: [
.usesFontLeading
],
context: nil
)
debugPrint("Calculated height: \(height)")
onHeightCalculated?(height.height)
label.attributedText = result
label.preferredMaxLayoutWidth = width
label.textAlignment = .left
return label
}
public func updateUIView(_ uiView: UIViewType, context: Context) {
uiView.sizeToFit()
}
public typealias UIViewType = UILabel
public let items: [AttributedItem]
public let width: CGFloat
public let lineHeightMultiple: CGFloat
public let onHeightCalculated: ((CGFloat) -> Void)?
public struct AttributedItem {
public let color: UIColor?
public var fontName: String
public var fontSize: CGFloat
public var text: String
public init(
fontName: String,
fontSize: CGFloat,
text: String,
color: UIColor? = nil
) {
self.fontName = fontName
self.fontSize = fontSize
self.text = text
self.color = color
}
}
public init(
items: [AttributedItem],
width: CGFloat,
lineHeightMultiple: CGFloat = 1,
onHeightCalculated: ((CGFloat) -> Void)? = nil
) {
self.items = items
self.width = width
self.lineHeightMultiple = lineHeightMultiple
self.onHeightCalculated = onHeightCalculated
}
}
I am using boundingRect function to calculate the height and passing it to the parent view to set the text view height correctly. Other vise text view getting random height values between 300 to 1000.
This approach only calculates single line height precisely. For multi line texts, text goes out of the calculated bound:
And this is my item component:
private func ItemView(_ item: AppNotification) -> some View {
HStack(alignment: .top, spacing: 10) {
Left(item)
SingleAxisGeometryReader(
axis: .horizontal,
alignment: .topLeading
) { width in
AttributedLabel(
items: [
.init(
fontName: AppFonts.hebooRegular.rawValue,
fontSize: 16,
text: item.data?.message ?? "",
color: UIColor(hexString: "#222222")
),
.init(
fontName: AppFonts.hebooRegular.rawValue,
fontSize: 16,
text: item.timeAgo ?? "",
color: UIColor(hexString: "#727783")
)
],
width: width,
lineHeightMultiple: 0.8,
onHeightCalculated: { textHeight = $0 }
)
.frame(alignment: .topLeading)
.onTapGesture {
if let deepLink = item.data?.deepLink {
viewStore.send(.openDeepLink(deepLink))
}
}
}
.frame(maxHeight: textHeight)
.background(Color.yellow)
Right(item)
}
.padding(.bottom, 16)
}
Can you see what is the problem? Or do you have an other approach?

Using JSON with sprite kit to set up a player who can level up

I am new to swift/spritekit, i was asked by my 8 year old to make him a basic game so i said yes (almost regretting it now lol)
What i am trying to achieve: a character who has the ability to "level up" based off experience points earned - I am using a JSON file to describe the levels
JSON file:
[
{
"id": 1,
"spriteTexture": "playerL1",
"weapon": "playerL1Weapon",
"expToLvlUp": 50,
"health": 2,
"attack": 1,
"defense": 1,
},
{
"id": 2,
"spriteTexture": "playerL2",
"weapon": "playerL2Weapon",
"expToLvlUp": 60,
"health": 7,
"attack": 2,
"defense": 2,
},
{
"id": 3,
"spriteTexture": "playerL3",
"weapon": "playerL3Weapon",
"expToLvlUp": 100,
"health": 10,
"attack": 5,
"defense": 5,
}
]
Decodable file:
import Foundation
extension Bundle{
func decode<T: Decodable>(_ Type: T.Type, from file: String) -> T {
guard let url = self.url(forResource: file, withExtension: nil) else {
fatalError("Failed to locate \(file) in bundle")
}
guard let data = try? Data(contentsOf: url) else{
fatalError("Failed to load \(file) from bundle")
}
let decoder = JSONDecoder()
guard let loaded = try? decoder.decode(T.self, from: data) else {
fatalError("Failed to decode /(file) from bundle")
}
return loaded
}
}
I have set up a struct to get the values from the JSON file:
import SpriteKit
struct PlayerLevel: Codable {
let id: Int
let spriteTexture: String
let weapon: String
let expToLvlUp: Int
let health: Int
let attack: Int
let defense: Int
}
and here is my class:
import Foundation
import SpriteKit
class Player: SKSpriteNode {
var type: PlayerLevel
{
didSet{
levelUp()
}
}
init(type: PlayerLevel){
self.type = type
let texture = SKTexture(imageNamed: type.spriteTexture)
super.init(texture: texture, color: .clear, size: texture.size())
position.x = -900
physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
physicsBody?.categoryBitMask = CollisionType.player.rawValue
physicsBody?.collisionBitMask = CollisionType.enemy.rawValue | CollisionType.enemyWeapon.rawValue
physicsBody?.contactTestBitMask = CollisionType.enemy.rawValue | CollisionType.enemyWeapon.rawValue
zPosition = 5
name = "player"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func shoot(){
let shot = SKSpriteNode(imageNamed: type.weapon)
shot.name = self.type.weapon
shot.size = CGSize(width: 50, height: 50)
shot.physicsBody = SKPhysicsBody(rectangleOf: shot.size)
shot.physicsBody?.categoryBitMask = CollisionType.playerWeapon.rawValue
shot.physicsBody?.collisionBitMask = CollisionType.enemy.rawValue | CollisionType.enemyWeapon.rawValue
shot.physicsBody?.contactTestBitMask = CollisionType.enemy.rawValue | CollisionType.enemyWeapon.rawValue
addChild(shot)
shot.zPosition = 3
shot.physicsBody?.affectedByGravity = false
let movement = SKAction.move(to: CGPoint(x: 4000, y: shot.position.y), duration: 1)
let sequence = SKAction.sequence([movement, .removeFromParent()])
shot.run(sequence)
}
func levelUp(){
self.texture = SKTexture(imageNamed: type.spriteTexture)
let texture = SKTexture(imageNamed: type.spriteTexture)
self.physicsBody = SKPhysicsBody(texture: texture, size: texture.size())
}
}
Firstly, i'm wondering if i have this set up the right way.
In my game scene:
import SpriteKit
import GameplayKit
enum CollisionType: UInt32 {
case player = 1
case playerWeapon = 2
case enemy = 4
case enemyWeapon = 8
case ground = 16
}
class GameScene: SKScene, SKPhysicsContactDelegate {
// Properties
var isPlayerAlive = true
var playerLevels = Bundle.main.decode([PlayerLevel].self, from: "player-levels.json")
let enemyTypes = Bundle.main.decode([EnemyType].self, from: "enemies.json")
var currentEnemy = 0
var currentLevel = 0 {
didSet {
player.type = playerLevels[currentLevel]
}
}
var player: Player!
var enemy: Enemy!
var levelUp: SKSpriteNode!
var healthLabel: SKLabelNode!
var scoreLabel: SKLabelNode!
let playerLevelLabel = SKLabelNode(fontNamed: "Chalkduster")
var score = 0 {
didSet{
scoreLabel.text = "Score: \(score)"
}
}
let ground = SKSpriteNode(imageNamed: "groundBottom")
let groundT = SKSpriteNode(imageNamed: "gameGroundTop")
var expLabel: SKLabelNode!
var exp = 0 {
didSet{
expLabel.text = "Experience Points: \(exp) / \(playerLevels[currentLevel].expToLvlUp)"
}
}
var health = 0 {
didSet{
healthLabel.text = "Health: \(playerLevels[currentLevel].health)"
}
}
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
makeGround()
initializePlayer()
spawnEnemy()
makeScoreLabel()
makeExpLabel()
makeHealthLabel()
}
func initializePlayer(){
player = Player(type: playerLevels[currentLevel])
addChild(player)
playerLevelLabel.text = "Details: \(player.type)"
playerLevelLabel.fontSize = 40
playerLevelLabel.position = CGPoint(x: 0, y: self.frame.minY + 200)
}
func makeScoreLabel(){
scoreLabel = SKLabelNode(fontNamed: "Chalkduster")
scoreLabel.position = CGPoint(x:self.frame.maxX - 250, y:self.frame.maxY - 100)
scoreLabel.horizontalAlignmentMode = .right
scoreLabel.fontSize = 40
scoreLabel.zPosition = 5
scoreLabel.text = "Score: 0"
scoreLabel.name = "score"
addChild(scoreLabel)
}
func makeExpLabel(){
expLabel = SKLabelNode(fontNamed: "Chalkduster")
expLabel.position = CGPoint(x:self.frame.minX + 500, y:self.frame.maxY - 110)
scoreLabel.fontSize = 40
expLabel.zPosition = 5
expLabel.text = "Expereience Points: 0 / \(playerLevels[currentLevel].expToLvlUp)"
expLabel.name = "exp"
addChild(expLabel)
}
func makeHealthLabel(){
healthLabel = SKLabelNode(fontNamed: "Chalkduster")
healthLabel.position = CGPoint(x:self.frame.minX + 1200, y:self.frame.maxY - 110)
healthLabel.fontSize = 40
healthLabel.zPosition = 5
healthLabel.text = "Health: \(playerLevels[currentLevel].health)"
healthLabel.name = "health"
addChild(healthLabel)
}
func makeGround(){
ground.name = "ground"
ground.position.x = frame.midX
ground.position.y = frame.minY
ground.zPosition = 1
addChild(ground)
ground.physicsBody = SKPhysicsBody(texture: ground.texture!, size: ground.texture!.size())
ground.physicsBody?.affectedByGravity = false
ground.physicsBody?.isDynamic = false
groundT.position.x = frame.midX
groundT.position.y = ground.position.y + 50
groundT.physicsBody?.isDynamic = false
addChild(groundT)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let touch = touches.first
let location = touch?.location(in: self)
let nodesAtLocation = nodes(at: location!)
for node in nodesAtLocation {
if node.name == "score"{
score += 10
exp += 5
upgrade()
}
if node.name == "attack"{
attack()
}
}
}
func upgrade() {
if exp < playerLevels[currentLevel].expToLvlUp { return }
if currentLevel + 1 > playerLevels.count - 1 {
gameOver()
return
}
if let explosion = SKEmitterNode(fileNamed: "Explosion") {
explosion.position = player.position
addChild(explosion)
}
//charLevelUp()
currentLevel += 1
playerLevelLabel.text = "Details: \(player.type)"
}
func didBegin(_ contact: SKPhysicsContact) {
guard let nodeA = contact.bodyA.node else { return }
guard let nodeB = contact.bodyB.node else { return }
let sortedNodes = [nodeA, nodeB].sorted { $0.name ?? "" < $1.name ?? ""}
let firstNode = sortedNodes[0]
let secondNode = sortedNodes[1]
if firstNode.name == "ground" { return }
if secondNode.name == "ground" { return }
if secondNode.name == "player" {
guard isPlayerAlive else { return }
if let explosion = SKEmitterNode(fileNamed: "explosion"){
explosion.position = firstNode.position
addChild(explosion)
}
//health -= 1
if playerLevels[currentLevel].health == 0{
gameOver()
secondNode.removeFromParent()
}
firstNode.removeFromParent()
} else if let enemy = firstNode as? Enemy {
enemy.health -= 1
if enemy.health == 0 {
if let explosion = SKEmitterNode(fileNamed: "Explosion") {
expLabel.position = enemy.position
addChild(explosion)
}
enemy.removeFromParent()
score += 50
exp = enemy.expGive
}
if let explosion = SKEmitterNode(fileNamed: "Explosion") {
explosion.position = enemy.position
addChild(explosion)
}
secondNode.removeFromParent()
} else {
if let explosion = SKEmitterNode(fileNamed: "Explosion") {
explosion.position = secondNode.position
addChild(explosion)
}
firstNode.removeFromParent()
secondNode.removeFromParent()
}
}
I've managed to get the sprite to change texture and physics body, the expReq points to change based on the players level, however, I can't seem to update the health property or any of the others. Any help would be appreciated - I'm not sure if I have this set up correctly - please advise :)

Mark Core Data entries as Favourites SWIFTUI

I am working on an app that stores posts from on-line Json to CoreData for offline content. It is a Real Estate Listings app where users sell and rent properties.
So far i accomplish to save fethed listings in CoreData but I can not implement "Add to Favourites" for the existing entries. I use NSPredicate to filter the listings types .in different views
I have created a entity "Listing" Core Data - Listing
And a "Favorite" entity Core Data - Favorites
My question is: How can i achieve "Add to Favourite"
Option A: Save the "is favourite" listing one more time in another Entity.
Option B: Create in the "Listing" Entity another "is favourite" property and keep it in Core Data as long as is favourite?
// Here is my Listing Model.swift
struct ListingModel: Hashable, Decodable, Encodable, Identifiable {
var id : Int
var title : String
var category : String
var name : String
var image : String
var publishdate : String
var saleprice : Int = 0
var rentprice : Int = 0
var listingtype : String
var latitude : Double!
var longitude : Double!
}
// JSONViewModel.swift
import SwiftUI
import CoreData
class JSONViewModel: ObservableObject {
#Published var listings: [ListingModel] = []
// #Published var tags :[Tags] = []
// saving Json to Core Data...
func saveData(contex: NSManagedObjectContext) {
listings.forEach { (data) in
let entity = Listing(context: contex)
entity.title = data.title
entity.name = data.name
entity.category = data.category
entity.image = data.image
entity.publishdate = data.publishdate
entity.tagline = data.tagline
entity.content = data.content
entity.coverimage = data.coverimage
entity.saleprice = Int64(truncating: NSNumber(value: data.saleprice))
entity.rentprice = Int64(truncating: NSNumber(value: data.rentprice))
entity.phone = data.phone
entity.email = data.email
entity.area = Int64(truncating: NSNumber(value: data.area))
entity.rooms = Int64(truncating: NSNumber(value: data.rooms))
entity.beds = Int64(truncating: NSNumber(value: data.beds))
entity.bathrooms = Int64(truncating: NSNumber(value: data.bathrooms))
entity.expires = data.expires
entity.adresa = data.adresa
entity.listingtype = data.listingtype
entity.latitude = Double(truncating: NSNumber(value: data.latitude))
entity.longitude = Double(truncating: NSNumber(value: data.longitude))
}
// }
// saving all pending data at once
do{
try contex.save()
print("success")
}
catch{
print(error.localizedDescription)
}
}
func fetchData(context: NSManagedObjectContext){
let url = "https://... my api adress"
var request = URLRequest(url: URL(string: url)!)
request.addValue("swiftui2.0", forHTTPHeaderField: "field")
let session = URLSession(configuration: .default)
session.dataTask(with: request) { (data, res, _) in
guard let jsonData = data else{return}
// check for errors
let response = res as! HTTPURLResponse
// checking by status code
if response.statusCode == 404 {
print("error Api Errror")
}
// fetching JSON Data ..
do {
let listings = try JSONDecoder().decode([ListingModel].self, from: jsonData)
DispatchQueue.main.async {
self.listings = listings
self.saveData(contex: context)
}
}
catch {
print(error.localizedDescription)
}
}
.resume()
}
// try to extend the function
func addListingToFavourites(favouritelisting:ListingModel) {
addRecordToFavourites(favouritelisting:favouritelisting.title)
}
func isListingFavourite(favouritelisting:ListingModel) -> Bool {
if let _ = fetchRecord(favouritelisting:favouritelisting.title) {
return true
}
return false
}
func removeListingFromFavourites(favouritelisting:ListingModel) {
removeRecordFromFavourites(favouritelisting:favouritelisting.title)
}
func toggleFavourite(favouritelisting:ListingModel) {
if isListingFavourite(favouritelisting: favouritelisting) {
removeListingFromFavourites(favouritelisting: favouritelisting)
}
else {
addListingToFavourites(favouritelisting: favouritelisting)
}
}
}
and i created also extension JSONViewModel:
extension JSONViewModel {
private func addRecordToFavourites(favouritelisting:String) {
guard let context = managedContext else {return}
if let record = fetchRecord(favouritelisting:favouritelisting) {
print("record \(record) already exists")
return
}
let entity = NSEntityDescription.entity(forEntityName: "Favourite",
in: context)!
let favourite = NSManagedObject(entity:entity, insertInto:context)
favourite.setValue(favouritelisting, forKeyPath:"favouritelisting")
do {
try context.save()
}
catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
self.changed = true
}
private func fetchRecord(favouritelisting:String) -> Favourite? {
guard let context = managedContext else {return nil}
let request = NSFetchRequest<Favourite>(entityName: "Favourite")
request.predicate = NSPredicate(format: "favouritelisting == %#", favouritelisting)
if let users = try? context.fetch(request) {
if users.count > 0 {
return users[0]
}
}
return nil
}
private func removeRecordFromFavourites(favouritelisting:String) {
guard let context = managedContext else {return}
if let record = fetchRecord(favouritelisting:favouritelisting) {
context.delete(record)
self.changed = true
}
}
}
::: Am i on the wright way? still i don't know it this what i should do!
Please find bellow Latest listingsView
import SwiftUI
struct latestListings: View {
#StateObject var jsonModel = JSONViewModel()
#Environment(\.managedObjectContext) var context
#FetchRequest(entity: Listing.entity(),
sortDescriptors:
[NSSortDescriptor(keyPath: \Listing.publishdate, ascending: false)])
var results : FetchedResults<Listing>
var textHeight: CGFloat = 60
var fullWidth: CGFloat = UIScreen.main.bounds.width
var cardWidthHalf: CGFloat = UIScreen.main.bounds.width / 2 + UIScreen.main.bounds.width / 3
var spacing: CGFloat = 10
var viewHeight: CGFloat = UIScreen.main.bounds.height / 2
#State private var isError = false
var body: some View {
VStack(alignment: .leading, spacing: 20) {
VStack(alignment: .leading, spacing: 10) {
HStack {
HStack {
Text("Latest Listings")
.modifier(textSectionTitle())
Spacer()
NavigationLink (destination: AllListingsVertical()) {
ViewMoreButton()
}.buttonStyle(PlainButtonStyle())
}
.padding(.trailing, 20)
}
Divider()
.modifier(dividerStyle())
HStack {
Image(systemName: "wand.and.stars.inverse")
.modifier(textSectionIcon())
Text("Manualy download listings to device storage. Useful for offline use.")
.modifier(textSectionTagline())
}
}
.padding(.top, 10)
.padding(.leading, 20)
.padding(.bottom, 0)
ScrollView(.horizontal, showsIndicators: false, content: {
HStack {
// checkin if core data exists
if results.isEmpty{
if jsonModel.listings.isEmpty{
HStack(alignment: .center) {
HStack(spacing: 10) {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: Color("dinamicPillsGrass")))
.scaleEffect(2, anchor: .center)
// fetching data
.onAppear(perform: {
jsonModel.fetchData(context: context)
})
}.frame(width: UIScreen.main.bounds.width)
}.modifier(cardHeight())
// when array is clear indicator appears
// as result data is fetched again
}
else{
HStack(spacing: 20) {
ForEach(jsonModel.listings,id: \.self){listing in
NavigationLink (destination: CardDetailView(listing: listing)) {
HStack {
CardViewOrizontal(listing: listing)
}
}.buttonStyle(PlainButtonStyle())
// display fetched Json Data..
}
}
}
}
else{
// results.prefix(?) unde ? cata articole sa arate
HStack(spacing: 20) {
ForEach(results.prefix(10)){listing in
NavigationLink (destination: CardDetailView(fetchedData: listing)) {
HStack {
CardViewOrizontal(fetchedData: listing)
}
}.buttonStyle(PlainButtonStyle())
}
}
.padding(.trailing, 15)
.padding(.leading, 15)
}
// update finish
}.padding(.top, 10)
.padding(.bottom, 10)
})
VStack(alignment: .center) {
Button(action: {
// clearing data in core data..
if Reachability.isConnectedToNetwork() {
//
do{
jsonModel.listings.removeAll()
results.forEach { (listing) in context.delete(listing) }
try context.save()
}
catch{
print(error.localizedDescription)
}
print("Network is connected")
self.isError = false
} else {
print("Network is not connected")
self.isError = true
}
}, label: {
HStack(alignment: .center) {
Image(systemName: "icloud.and.arrow.down")
.modifier(textSectionIcon())
Text("Update")
.modifier(textSectionTagline())
}
.padding(5)
.padding(.trailing, 5)
.background(Color("blueLeading"))
.cornerRadius(20)
.modifier(shadowPills())
}).alert(isPresented: $isError) {
Alert(title: Text("Network is not connected"),
message: Text("WiFi or Cellular not availible. You can still browse offline content!"),
dismissButton: .default(Text("OK")))
}
}.frame(width: fullWidth)
}
.padding(.top, 10)
.padding(.bottom, 60)
.frame(width: fullWidth)
.background(LinearGradient(gradient: Gradient(colors: [Color("dinamicGray1"), Color("dinamicGray2")]), startPoint: .top, endPoint: .bottom))
.cornerRadius(20)
.padding(.top, -50)
.modifier(shadowSection())
}
}
And CardDetailView
//
// CardDetailView.swift
// WebyCoreData
//
// Created by Marius Geageac on 20.12.2020.
//
import SwiftUI
import KingfisherSwiftUI
import MapKit
struct CardDetailView: View {
// noul liked
var fullWidth: CGFloat = UIScreen.main.bounds.width
var halfScreenH: CGFloat = UIScreen.main.bounds.height / 2
#ObservedObject var settingsVM = SettingsViewModel()
#State private var isVisible = false
var listing: ListingModel?
var fetchedData: Listing?
// Modifiers
var cardWidth: CGFloat = UIScreen.main.bounds.width
var imageWidth: CGFloat = UIScreen.main.bounds.width
/// map
var locationCoordinate: CLLocationCoordinate2D {
CLLocationCoordinate2D(
latitude: listing == nil ? fetchedData!.latitude : listing!.latitude,
longitude: listing == nil ? fetchedData!.longitude : listing!.longitude)
}
let paddingPills: CGFloat = 5
let textSizePills: CGFloat = 14
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 5) {
HStack {
Text(listing == nil ? fetchedData!.title! : listing!.title)
.modifier(textSectionTitle())
}
Divider()
.modifier(dividerStyle())
HStack {
Image(systemName: "info.circle")
.modifier(textSectionIcon())
Text(listing == nil ? fetchedData!.tagline! : listing!.tagline)
.modifier(textSectionTagline())
}
}
.padding(.top, 10)
.padding(.leading, 20)
.padding(.bottom, 0)
if self.fetchedData!.isFavorite == false {
Button(action: {
fetchedData!.isFavorite.toggle()
}) {
Image(systemName: "heart.circle")
.modifier(textSectionIcon())
}.padding()
}
else {
Button(action: {
fetchedData!.isFavorite.toggle()
}) {
Image(systemName: "heart.circle.fill")
.modifier(textSectionIcon())
}.padding()
}
}
}
}
import SwiftUI
struct Favorites: View {
#StateObject var jsonModel = JSONViewModel()
var cardWidth: CGFloat = UIScreen.main.bounds.width - 30
var fullWidth: CGFloat = UIScreen.main.bounds.width
// #StateObject var jsonModel = JSONViewModel()
#Environment(.managedObjectContext) var context
// Fetching Data From Core Data..
#FetchRequest(entity: Listing.entity(), sortDescriptors:
// [NSSortDescriptor(keyPath: \Listing.publishdate, ascending: false)])
[NSSortDescriptor(keyPath: \Listing.publishdate, ascending: false),],predicate: NSPredicate(format: "isFavourite == %#" , NSNumber(value: true)))
var results : FetchedResults<Listing>
var body: some View {
ScrollView(.vertical) {
VStack(alignment: .center) {
VStack(alignment: .center) {
LazyVStack(spacing: 20) {
ForEach(results){listing in
NavigationLink (destination: CardDetailView(fetchedData: listing)) {
VStack {
CardView(fetchedData: listing)
}.frame(width: UIScreen.main.bounds.width)
.modifier(cardHeight())
}.buttonStyle(PlainButtonStyle())
}
}
}
}.padding(.top, 20)
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
allListingsTitlePill() // Title
}
ToolbarItem(placement: .navigationBarTrailing){
HStack {
Button(action: {
}, label: {
Image(systemName: "heart.circle.fill")
.font(.system(size: 40, weight: .regular))
})
}
}
}
}
}
I would personally just add another property in your listings entity called isFavourite and set it to a boolean. set it initially to false.
Then when you are doing your fetch requests, you can only show favourites using a predicate like this.
let predicateIsFavourite = NSPredicate(format: "isFavourite == %#", NSNumber(value: true))
and in lists / ForEach, you display whether it is a favourite and use a button to toggle it being a favourite.
The toggle would just set the value of isFavourite to true (let me know if you would like some code for that but looking at your question, it seems like you know how to do that)

Buttons and some user interactions are not responding with iOS14 app

I am struggling with a strange bug with my iOS app. When using a simulator with iOS 13 application works as it should, but when using iOS 14 buttons, switches, and other functionalities are not responding. There is no error output in the console.
I don't understand why this happens only with XCode 11 and iOS 14.
This a snippet with initializing one of my buttons in the View.
let logButton: UIButton = {
let button = UIButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.setLogInButton()
button.setTitle(NSLocalizedString("log_in", comment: ""), for: .normal)
return button
}()
Here I'm assigning the target to the button.
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initConstraints()
backgroundColor = UIColor().settingsTableViewCellColor()
logButton.addTarget(self, action: #selector(buttonLogInLogOut), for: .touchUpInside)
}
There is the action
#objc func buttonLogInLogOut(_ sender: UIButton){
print("Log in clicked")
delegate?.logInLogOutFunc(sender)
}
As I said the buttons (switches, and others) are not responding ONLY in iOS 14.
It looks like targetActions are not working.
Thanks for any kind of help.
Regards
Matt
I had the same problem with a button in a table cell not working
For some reason you have to add the button to the contentView of the cell instead of the cell itself as follows
cell.contentView.addSubView(button)
Worked for me afterwards
Just follow this solution
For Cell:
class CommentCell: UICollectionViewCell {
private let commentViewCell: CommentViewCell = {
let view = CommentViewCell()
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(commentViewCell)
NSLayoutConstraint.activate([
commentViewCell.leadingAnchor.constraint(equalTo: leadingAnchor),
commentViewCell.trailingAnchor.constraint(equalTo: trailingAnchor),
commentViewCell.topAnchor.constraint(equalTo: topAnchor),
commentViewCell.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setData(item: CommentViewModel) {
commentViewCell.setData(item: item)
}
}
For View:
class CommentViewCell: UIView {
private let lblId: CustomClick = {
let view = CustomClick()
view.backgroundColor = .cyan
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let lblMessage: UILabel = {
let view = UILabel()
view.backgroundColor = .cyan
view.textColor = .black
view.font = .boldSystemFont(ofSize: 16)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
private let lblDate: UILabel = {
let view = UILabel()
view.backgroundColor = .systemIndigo
view.textColor = .black
view.font = .boldSystemFont(ofSize: 16)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .brown
addSubview(lblId)
addSubview(lblMessage)
addSubview(lblDate)
NSLayoutConstraint.activate([
lblId.topAnchor.constraint(equalTo: topAnchor),
lblId.trailingAnchor.constraint(equalTo: trailingAnchor),
lblId.leadingAnchor.constraint(equalTo: leadingAnchor),
lblMessage.trailingAnchor.constraint(equalTo: trailingAnchor),
lblMessage.leadingAnchor.constraint(equalTo: leadingAnchor),
lblMessage.topAnchor.constraint(equalTo: lblId.bottomAnchor),
lblDate.trailingAnchor.constraint(equalTo: trailingAnchor),
lblDate.leadingAnchor.constraint(equalTo: leadingAnchor),
lblDate.topAnchor.constraint(equalTo: lblMessage.bottomAnchor),
])
lblId.addTarget(self, action: #selector(onClick), for: .touchUpInside)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func onClick() {
print("this is click button")
}
func setData(item: CommentViewModel) {
lblId.setData(item: item.id)
lblMessage.text = item.meesage
lblDate.text = item.date
lblDate.textColor = item.cellColor
}
class CustomClick: UIControl {
private let lblId: UILabel = {
let view = UILabel()
view.textColor = .black
view.backgroundColor = .systemPink
view.font = .boldSystemFont(ofSize: 16)
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(lblId)
NSLayoutConstraint.activate([
lblId.topAnchor.constraint(equalTo: topAnchor),
lblId.trailingAnchor.constraint(equalTo: trailingAnchor),
lblId.leadingAnchor.constraint(equalTo: leadingAnchor),
lblId.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setData(item: String) {
lblId.text = item
}
}
}

Refresh JSON data after a few seconds in swift

I want this to update every 4 seconds with fresh data form the url, but i dont know how to do this. This is what i have so far and it works fine but without the refresher! The Refresher needs to work like a youtube subscriber counter that update every 4 seconds or so. I have looked at a timer but i couldn't make it work because (i think) its a searchBarSearchButtonClicked function and the urlRequestid has to have a input! Please help! Thanks!
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
let urlRequestid = URLRequest(url: URL(string: "https://www.mylink.com/\(searchBar.text!.replacingOccurrences(of: " ", with: "%20"))/?__a=1")!)
if (interstitial.isReady){
interstitial.present(fromRootViewController: self)
interstitial = createAndLoadInterstitial()
}
let task = URLSession.shared.dataTask(with: urlRequestid) { (data, response, error) in
if error == nil {
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! [String : AnyObject]
if let user = json["user"] as? [String : AnyObject] {
let profile_pic_url_hd = user["profile_pic_url_hd"] as! String
let urlstr = "\(profile_pic_url_hd)"
if var comps = URLComponents(string: urlstr) {
var path = comps.path
var pathComps = path.components(separatedBy: "/")
pathComps.remove(at: 2) // this removes the s320x320
path = pathComps.joined(separator: "/")
comps.path = path
if let newStr = comps.string {
print(newStr)
self.imgURL = "\(newStr)"
}
}
if let bio = user["biography"] as? String {
self.bioS = bio
}
if let naam = user["username"] as? String {
self.naamS = naam
}
if let followed_by = user["followed_by"] as? [String : AnyObject] {
self.VolgS = followed_by["count"] as! Int
}
if let follows = user["follows"] as? [String : AnyObject] {
self.volgD = follows["count"] as! Int
}
if let media = user["media"] as? [String : AnyObject] {
self.postS = media["count"] as! Int
}
}
if let _ = json["error"] {
self.exists = false
}
DispatchQueue.main.async {
if self.exists{
self.imgView.downloadImage(from: self.imgURL!)
self.naam.text = "#\(self.naamS ?? "")"
if self.bioS == nil {
self.bio.text = "This Person has no biography!"
} else {
self.bio.text = "\(self.bioS ?? "")"
}
self.volgers.text = "\(self.VolgS!)"
self.volgend.text = "\(self.volgD!)"
self.post.text = "\(self.postS!)"
} else {
self.exists = true
}
}
} catch let jsonError {
print(jsonError.localizedDescription)
}
}
}
task.resume()
}
}
One quick but admittedly clumsy fix would be to store the latest UISearchBar instance from the searchBarSearchButtonClicked parameter in a local instance variable:
var currentSearch: UISearchBar = UISearchBar()
var timer: Timer?
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
currentSearch = searchBar
// Add the rest of the method code below...
...
}
// Call this method to begin repetition
func repeatSearch() {
self.timer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true,
block: { (timer) in
self.searchBarSearchButtonClicked(self.currentSearch)
})
}
You can achieve it by using the Timer, schedule it for every 4 seconds.
DEMO
FOR iOS 10.0 and Above
var timer: Timer?
func callMe() {
func doSomrThing(str: String) {
print(str)
}
doSomrThing(str: "first time")
self.timer = Timer.scheduledTimer(withTimeInterval: 4.0, repeats: true, block: { (timer) in
doSomrThing(str: "after 4 second")
})
}
For below iOS 10.0
var timer: Timer?
func callMe() {
self.doSomeThing(str: "first time")
self.timer = Timer.scheduledTimer(timeInterval: 4.0, target: self, selector: #selector(AddTextVC.timerHandler), userInfo: nil, repeats: true)
}
func doSomeThing(str: String) {
print(str)
}
func timerHandler() {
self.doSomeThing(str: "after 4 seconds")
}
Just replace your code according to the demo.
And add this code to your viewController :
deinit {
self.timer?.invalidate()
self.timer = nil
}