Related
i have an issue with ordering, combining and positioning of my solid shapes and text
the task is simple enough, but while im completely newbie in swfitui i cannot do it for my self
task: place solid shapes with zstack relative by their parrent using coordinates (including text of each shape) and apply isometric/perpective modifier
i will be glad for explanations and hints
this is what i have done for now
works without modifiers
and no with isometric modifier
struct ContentView: View {
#ObservedObject var datas = ReadData()
//MagnificationState
//DragState
//#GestureState
//#State viewMagnificationState
//#GestureState
//#State viewDragState
//magnificationScale
//translationOffset
//#State popoverState
//#State selectedShape
var body: some View {
//magnificationGesture
//dragGesture
//magnificationGesture.simultaneously
GeometryReader { geometry in
ZStack(alignment: .topLeading) {
MainShape(showingPopover: $popoverState,
setCurrentShape: $selectedShape,
alldatas: datas.pokedex)
}
.scaleEffect(magnificationScale)
.offset(translationOffset)
//.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
//.gesture(both)
//popover
}
.edgesIgnoringSafeArea(.all)
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct MainShape: View {
let alldatas: [PokedexElement]
var body: some View {
ForEach(alldatas, id: \.id) { shape in
ZStack(alignment: .topLeading) {
Text(shape.metaProperties.name)
.font(.system(size: 20))
.zIndex(2)
.offset(x: shape.metaProperties.offset.x, y: shape.metaProperties.offset.y)
Rectangle()
.cornerRadius(shape.id == 1 ? 55 : 10)
.foregroundColor(chooseColor(shape.metaProperties.color))
.offset(x: shape.metaProperties.offset.x, y: shape.metaProperties.offset.y)
.frame(
width: shape.metaProperties.size.width,
height: shape.metaProperties.size.height
)
.isometric()
.zIndex(1)
.onTapGesture {
self.showingPopover = true
self.setCurrentShape = shape.id
}
}
}
}
}
data:
[
{
"id": 1,
"parentId": 0,
"baseProperties": {
"name": "",
"descr": "",
"contacts": ""
},
"metaProperties": {
"name": "root",
"status": true,
"type": "plain",
"size": {
"width": 350,
"height": 350
},
"offset": {
"x": 0,
"y": 0
},
"color": "blue"
}
},
{
"id": 2,
"parentId": 1,
"baseProperties": {
"name": "object 1",
"descr": "",
"contacts": ""
},
"metaProperties": {
"name": "child 1",
"status": true,
"type": "imobject",
"size": {
"width": 50,
"height": 50
},
"offset": {
"x": 50,
"y": 50
},
"color": "red"
}
},
{
"id": 3,
"parentId": 1,
"baseProperties": {
"name": "object 1",
"descr": "",
"contacts": ""
},
"metaProperties": {
"name": "child 2",
"status": true,
"type": "imobject",
"size": {
"width": 100,
"height": 50
},
"offset": {
"x": 100,
"y": 50
},
"color": "green"
}
}
]
and modifiers…
extension View {
func chooseColor(_ name: String) -> Color {
switch name {
case "red":
return Color.red
case "blue":
return Color.blue
case "brown":
return Color.brown
case "green":
return Color.green
case "yellow":
return Color.yellow
case "white":
return Color.white
default :
return Color.black
}
}
func perspective() -> some View {
self
.rotation3DEffect(.degrees(45), axis: (x: 1, y: 0, z: 0))
}
func isometric() -> some View {
self
.rotationEffect(Angle.init(degrees: 45))
.scaleEffect(y: 0.5)
}
}
task was easy, instead of using regular solid shapes with "magic methods from black box" do it all manually
convert decart to iso and place it whatever you want together with text or anything else and offset will be fine
I'm building a chat app using SwiftUI and I'm having difficulties getting a LazyVStack to work inside a ScrollView. Here are my questions:
Question 1
Where I have .id(message.id), why is this id required for scrollView.scrollTo(chatMessageViewModel.arrayOfMessages.last?.id, anchor: .bottom) to work, when the ForEach is assigning the same id using id: \.1.id? If I comment out the .id(message.id) line, scrollView.scrollTo doesn't work.
Question 2:
a) If I comment out the code .id(message.id),on an iPhone 6S, I get 11 messages loaded in view, however, the print statement print(message.messageContent) prints out 22 messages. Why does this happen?
b) Why are the print(message.messageContent) print statements not printed in order? I thought a LazyVStack would render in vertical order?
c) As I scroll down to reveal the 12th message, I get "Message 23" printed to the console instead of "Message 12". Why is this?
import SwiftUI
struct ChatMessageModel: Identifiable {
var id: String
var messageContent: String
}
class ChatMessageViewModel: ObservableObject {
#Published var arrayOfMessages: [ChatMessageModel] = [ChatMessageModel(id: "1", messageContent: "Message 1"),
ChatMessageModel(id: "2", messageContent: "Message 2"),
ChatMessageModel(id: "3", messageContent: "Message 3"),
ChatMessageModel(id: "4", messageContent: "Message 4"),
ChatMessageModel(id: "5", messageContent: "Message 5"),
ChatMessageModel(id: "6", messageContent: "Message 6"),
ChatMessageModel(id: "7", messageContent: "Message 7"),
ChatMessageModel(id: "8", messageContent: "Message 8"),
ChatMessageModel(id: "9", messageContent: "Message 9"),
ChatMessageModel(id: "10", messageContent: "Message 10"),
ChatMessageModel(id: "11", messageContent: "Message 11"),
ChatMessageModel(id: "12", messageContent: "Message 12"),
ChatMessageModel(id: "13", messageContent: "Message 13"),
ChatMessageModel(id: "14", messageContent: "Message 14"),
ChatMessageModel(id: "15", messageContent: "Message 15"),
ChatMessageModel(id: "16", messageContent: "Message 16"),
ChatMessageModel(id: "17", messageContent: "Message 17"),
ChatMessageModel(id: "18", messageContent: "Message 18"),
ChatMessageModel(id: "19", messageContent: "Message 19"),
ChatMessageModel(id: "20", messageContent: "Message 20"),
ChatMessageModel(id: "21", messageContent: "Message 21"),
ChatMessageModel(id: "22", messageContent: "Message 22"),
ChatMessageModel(id: "23", messageContent: "Message 23"),
ChatMessageModel(id: "24", messageContent: "Message 24"),
ChatMessageModel(id: "25", messageContent: "Message 25")]
}
struct ChatMessagesView: View {
#StateObject var chatMessageViewModel = ChatMessageViewModel()
var body: some View {
ScrollViewReader { scrollView in
ScrollView (.vertical, showsIndicators: true) {
LazyVStack (spacing: 0) {
ForEach(Array(zip(chatMessageViewModel.arrayOfMessages.indices, chatMessageViewModel.arrayOfMessages)), id: \.1.id) { (index, message) in
Text("Index is \(index) with message: \(message.messageContent)")
.padding(.vertical, 20)
.id(message.id)
.onAppear {
print(message.messageContent)
}
}
}
}
.onAppear {
scrollView.scrollTo(chatMessageViewModel.arrayOfMessages.last?.id, anchor: .bottom)
}
}
.environmentObject(chatMessageViewModel)
}
}
What you are seeing is the intented way the LazyVStack works, it will create and destroy views as needed LazyVStack, that's why the onAppear is called when you are scrolling, about the ordering as you can see the order is fine swiftui handles the views creation in its own cycle.
As I scroll down to reveal the 12th message, I get "Message 23" printed to the console instead of "Message 12". Why is this?
That's because the bounce animation when you reach the bottom, the upper message will be recreated.
struct ChatMessagesView: View {
#StateObject var chatMessageViewModel = ChatMessageViewModel()
var body: some View {
ScrollViewReader { scrollView in
ScrollView (.vertical, showsIndicators: true) {
LazyVStack (spacing: 0) {
ForEach(chatMessageViewModel.arrayOfMessages.indices, id: \.self) { index in
let message = chatMessageViewModel.arrayOfMessages[index]
Text("Index is \(index) with message: \(message.messageContent)")
.padding(.vertical, 20)
.id(message.id)
.onAppear {
print(message.messageContent)
}
}
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
withAnimation{
scrollView.scrollTo(chatMessageViewModel.arrayOfMessages.last?.id)
}
}
}
}
.environmentObject(chatMessageViewModel)
}
}
I would add some animation and the timeout so the scroll looks smoother.
Container Size depends on the widget's child length
I want all things in the mobile screen height not the inner child scroll or outer
I expected output like below -
what I am getting -
import 'package:flutter/material.dart';
class ExpandableContainer extends StatefulWidget {
const ExpandableContainer({Key? key}) : super(key: key);
#override
State<ExpandableContainer> createState() => _ExpandableContainerState();
}
class _ExpandableContainerState extends State<ExpandableContainer> {
int selectedIndex = -1;
List dataList = [
{
"title": "Title 1",
"items": [
'Item 1',
'Item 2',
'Item 3',
'Item 4',
],
},
{
"title": "Title 2",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
],
},
{
"title": "Title 3",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
],
},
{
"title": "Title 4",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9",
"Item 10",
],
},
{
"title": "Title 5",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
],
},
];
#override
Widget build(BuildContext context) {
final Size size = MediaQuery.of(context).size;
return Scaffold(
backgroundColor: Colors.white,
body: SizedBox(
height: size.height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(
dataList.length,
(index) => GestureDetector(
onTap: () => setState(() {
selectedIndex = index;
}),
child: Container(
height: size.height / dataList.length,
width: size.width,
padding: const EdgeInsets.fromLTRB(32.0, 16.0, 0.0, 16.0),
// alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 8.0, color: Colors.grey),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
dataList[index]["title"].toUpperCase(),
style: const TextStyle(
fontSize: 38.0,
color: Colors.black,
fontWeight: FontWeight.w900,
letterSpacing: -2.0,
),
),
for (var item in dataList[index]["items"])
Text(
selectedIndex == index ? item : "",
style: TextStyle(
fontSize: 20.0,
color: Colors.black,
height: selectedIndex == index ? 1.5 : 0.0,
),
),
],
),
),
),
),
),
),
);
}
}
List<String> category = [
"HealthCare",
"Food & Drink",
"Beauty",
"Baby & kids",
"Homeware"
];
List<Color> color = [
Colors.lightBlueAccent,
Colors.green.shade700,
Colors.pinkAccent.shade100,
Colors.blueAccent,
Colors.amber,
];
List<Color> textColor = [
Colors.blueAccent.shade700,
Colors.amber,
Colors.pinkAccent.shade700,
Colors.pinkAccent.shade100,
Colors.white,
];
int? selectedIndex;
List<dynamic> dataList = [
{
"title": "Title 1",
"items": [
'Item 1',
'Item 2',
'Item 3',
'Item 4',
],
},
{
"title": "Title 2",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
],
},
{
"title": "Title 3",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
],
},
{
"title": "Title 4",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
"Item 8",
"Item 9",
"Item 10",
],
},
{
"title": "Title 5",
"items": [
"Item 1",
"Item 2",
"Item 3",
"Item 4",
"Item 5",
"Item 6",
"Item 7",
],
},
];
Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: [
...category.map(
(index) => Expanded(
flex: selectedIndex==category.indexOf(index) ? (dataList[category.indexOf(index)]["items"].length/3).ceil(): 1,
child: GestureDetector(
onTap: () {
print(dataList[category.indexOf(index)]["items"].toString());
print("tap");
setState(() {
selectedIndex = category.indexOf(index);
});
},
child: Container(
color: color[category.indexOf(index)],
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
index.toUpperCase(),
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: textColor[category.indexOf(index)]),
),
selectedIndex == category.indexOf(index)
? Column(
children: [
...dataList[category.indexOf(index)]
["items"]
.map(
(e) => Text(e),
),
// ListTile(title:Text(dataList[category.indexOf(index)]["items"].toString()))
],
)
: SizedBox(),
],
),
),
),
),
),
),
],
),
);
Please use ExpansionTile for expansion
ExpansionTile(
iconColor:Colors.blue,
textColor:Colors.black,
title: Text('Privacy Policy',style: TextStyle(fontSize: 16)),
children: [
ListTile(
title: Text('item1',textAlign: justifyText,),
),
ListTile(
title: Text('item2',textAlign: justifyText,),
),
ListTile(
title: Text('item3',textAlign: justifyText,),
),
ListTile(
title: Text('item4',textAlign: justifyText,),
),
],
),
Try this you can test this on dartpad its so now the one is hindering the size was on the container so if you select a certain index make the container height to null so that it will wrap the results inside.
return Scaffold(
backgroundColor: Colors.white,
body: SizedBox(
child: ListView.builder(
shrinkWrap: true,
itemCount: dataList.length,
itemBuilder:(context,index)
=>GestureDetector(
onTap:(){
setState((){
selectedIndex = index;
print( dataList[index]['items'].map((e)=> e).toList());
});
},
child: Container(
height: selectedIndex == index ? null
: size.height / dataList.length,
width: size.width,
padding: const EdgeInsets.fromLTRB(32.0, 16.0, 0.0, 16.0),
// alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: 8.0, color: Colors.grey),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
dataList[index]["title"].toUpperCase(),
style: const TextStyle(
fontSize: 38.0,
color: Colors.black,
fontWeight: FontWeight.w900,
letterSpacing: -2.0,
),
),
...dataList[index]['items'].map((e)=>
selectedIndex == index ?
Text(
e,
style: TextStyle(
fontSize: 20.0,
color: Colors.black,
height: selectedIndex == index ? 1.5 : 0.0,
) ,
) : Container(),).toList()
],
),
),
),
),
),
);
Solution No 1
To achieve this you can use ExpansionTile class,
This widget is typically used with ListView to create an "expand / collapse" list entry. When used with scrolling widgets like ListView, a unique PageStorageKey must be specified to enable the ExpansionTile to save and restore its expanded state when it is scrolled in and out of view.
this is the link about ExpansionTile
https://api.flutter.dev/flutter/material/ExpansionTile-class.html#:~:text=ExpansionTile%20class%20Null%20safety,expand%20%2F%20collapse%22%20list%20entry.
and this is the demo example link how to do it
https://rrtutors.com/tutorials/Expandable-Listview-Flutter-ExpansionTile
Solution No 2
Use ExpandableGroup
To achieve this you can use ExpandableGroup
this is the link about ExpandableGroup
https://pub.dev/packages/expandable_group
if you don't want to use plugin , just inspect this plugin code , it has simple logic
You can use expansion tile but if the existing expansion tile does not provide the view required for your project, you can create your own custom expansion tile widget.
Like
app_expansion_tile
You should use expansion tile, as far as i have saw in your image you don't want it to be scrollablle, what i suggest is to make a limitation on the expansiontile class or the child, then set restriction limit to with and height and make it be responsive.
Scenario: Accessing data (of variable size) from a server and display it. In this case, an array of countries.
Server access should be called once(1) upon entering this View (or before).
Observation: I can see the data via debugger & print statements within the publisher.
Problem: Data doesn't display within View.
The following is the ENTIRE code (reduced to a simple request for an array of countries via playground).
Please feel free to cut/paste into playground.
It should run as expected, but doesn't display any data.
Solution?
import Combine
import SwiftUI
import PlaygroundSupport
// ---------------------------------------------------------------------------
var cancellables: Set<AnyCancellable> = []
struct VaccinesDataView: View {
#State private var appleData: [String]?
#StateObject var appleCountries = AppleCountries()
var body: some View {
Form {
Section(header: VStack(alignment: .leading) {
Text("Apple Data")
}) {
if let vacData = appleData {
ForEach(vacData, id: \.self) { source in
HStack {
Text(source)
.font(.title)
}
}
}
}
.navigationBarTitle("Vaccines", displayMode: .inline)
}.onAppear {
self.appleCountries.getData()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
// =====================================================================================================
class AppleCountries: ObservableObject {
#Published var appleData: [String]?
func getData() {
let appleURL = URL(string: "https://disease.sh/v3/covid-19/apple/countries")!
let remoteDataPublisher = URLSession.shared.dataTaskPublisher(for: appleURL)
.map(\.data)
.receive(on: DispatchQueue.main)
.decode(type: [String].self, decoder: JSONDecoder())
.print("Apples: ")
remoteDataPublisher
.eraseToAnyPublisher()
.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Publisher Finished")
case let .failure(anError):
Swift.print("\nReceived error: ", anError)
}
}, receiveValue: { someValue in
self.appleData = someValue
print("appleData: \(String(describing: self.appleData))")
}).store(in: &cancellables)
}
}
PlaygroundPage.current.setLiveView(VaccinesDataView())
Hint: I notice REPEATED access to server, when I only want one (1).
The server access is via .onAppear() which is probably called multiple times.
Apples: : receive subscription: (Decode)
Apples: : request unlimited
Apples: : receive subscription: (Decode)
Apples: : request unlimited
Apples: : receive subscription: (Decode)
Apples: : request unlimited
Apples: : receive subscription: (Decode)
Apples: : request unlimited
Apples: : receive value: (["Albania", "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Bulgaria", "Cambodia", "Canada", "Chile", "Colombia", "Croatia", "Czechia", "Denmark", "Egypt", "Estonia", "Finland", "France", "Germany", "Greece", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Ireland", "Israel", "Italy", "Japan", "Latvia", "Lithuania", "Luxembourg", "Macao", "Malaysia", "Mexico", "Morocco", "Netherlands", "New Zealand", "Norway", "Philippines", "Poland", "Portugal", "S. Korea", "Romania", "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia", "South Africa", "Spain", "Sweden", "Switzerland", "Taiwan", "Thailand", "Turkey", "Ukraine", "UAE", "UK", "USA", "Uruguay", "Vietnam"])
appleData: Optional(["Albania", "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Bulgaria", "Cambodia", "Canada", "Chile", "Colombia", "Croatia", "Czechia", "Denmark", "Egypt", "Estonia", "Finland", "France", "Germany", "Greece", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Ireland", "Israel", "Italy", "Japan", "Latvia", "Lithuania", "Luxembourg", "Macao", "Malaysia", "Mexico", "Morocco", "Netherlands", "New Zealand", "Norway", "Philippines", "Poland", "Portugal", "S. Korea", "Romania", "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia", "South Africa", "Spain", "Sweden", "Switzerland", "Taiwan", "Thailand", "Turkey", "Ukraine", "UAE", "UK", "USA", "Uruguay", "Vietnam"])
Apples: : receive finished
Publisher Finished
Apples: : receive value: (["Albania", "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Bulgaria", "Cambodia", "Canada", "Chile", "Colombia", "Croatia", "Czechia", "Denmark", "Egypt", "Estonia", "Finland", "France", "Germany", "Greece", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Ireland", "Israel", "Italy", "Japan", "Latvia", "Lithuania", "Luxembourg", "Macao", "Malaysia", "Mexico", "Morocco", "Netherlands", "New Zealand", "Norway", "Philippines", "Poland", "Portugal", "S. Korea", "Romania", "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia", "South Africa", "Spain", "Sweden", "Switzerland", "Taiwan", "Thailand", "Turkey", "Ukraine", "UAE", "UK", "USA", "Uruguay", "Vietnam"])
appleData: Optional(["Albania", "Argentina", "Australia", "Austria", "Belgium", "Brazil", "Bulgaria", "Cambodia", "Canada", "Chile", "Colombia", "Croatia", "Czechia", "Denmark", "Egypt", "Estonia", "Finland", "France", "Germany", "Greece", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Ireland", "Israel", "Italy", "Japan", "Latvia", "Lithuania", "Luxembourg", "Macao", "Malaysia", "Mexico", "Morocco", "Netherlands", "New Zealand", "Norway", "Philippines", "Poland", "Portugal", "S. Korea", "Romania", "Russia", "Saudi Arabia", "Serbia", "Singapore", "Slovakia", "Slovenia", "South Africa", "Spain", "Sweden", "Switzerland", "Taiwan", "Thailand", "Turkey", "Ukraine", "UAE", "UK", "USA", "Uruguay", "Vietnam"])
Apples: : receive finished
Publisher Finished
...
...
...
You don't need extra state appleData - it is not filled - use appleCountries directly as below.
Tested with Xcode 12.1 / iOS 14.1
struct VaccinesDataView: View {
#StateObject var appleCountries = AppleCountries()
var body: some View {
Form {
Section(header: VStack(alignment: .leading) {
Text("Apple Data")
}) {
if let vacData = appleCountries.appleData { // !!
ForEach(vacData, id: \.self) { source in
HStack {
Text(source)
.font(.title)
}.onAppear {
appleCountries.getData()
}
}
}
}
.navigationBarTitle("Vaccines", displayMode: .inline)
}.onAppear {
self.appleCountries.getData()
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
I need to extract the following four-character matches from the below string:
Sites:
KPAE
KPSC
KPUW
KRNT
KSEA
KSFF
KSHN
KTIW
Data:
{"sids": ["24222 1", "452670 2", "PAE 3", "KPAE 5", "USW00024222 6"], "name": "EVERETT SNOHOMISH AP"},
{"sids": ["24163 1", "PSC 3", "KPSC 5", "USW00024163 6"], "name": "PASCO TRI CITIES AP"},
{"sids": ["94129 1", "PUW 3", "KPUW 5", "USW00094129 6"], "name": "PULLMAN MOSCOW RGNL AP"},
{"sids": ["94248 1", "RNT 3", "KRNT 5", "USW00094248 6"], "name": "RENTON MUNI AP"},
{"sids": ["24233 1", "457473 2", "SEA 3", "72793 4", "KSEA 5", "USW00024233 6", "SEA 7"], "name": "SEATTLE TACOMA INTL AP"},
{"sids": ["94176 1", "SFF 3", "KSFF 5", "USW00094176 6"], "name": "SPOKANE FELTS FLD"},
{"sids": ["94227 1", "457585 2", "SHN 3", "KSHN 5", "USW00094227 6", "SHN 7"], "name": "SHELTON SANDERSON FLD"},
{"sids": ["94274 1", "TIW 3", "KTIW 5", "USW00094274 6"], "name": "TACOMA NARROWS AP"},
I have tried to extract these matches from the strings but, the position of them can change from string to string...
Attempted Code:
awk -F',' '{print $5}'
Using grep -oP:
grep -Po '"\K[A-Z]{4}\b' file
KPAE
KPSC
KPUW
KRNT
KSEA
KSFF
KSHN
KTIW